summaryrefslogtreecommitdiffstats
path: root/js/src/threading
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/threading')
-rw-r--r--js/src/threading/ConditionVariable.h121
-rw-r--r--js/src/threading/ExclusiveData.h197
-rw-r--r--js/src/threading/LockGuard.h52
-rw-r--r--js/src/threading/Mutex.cpp74
-rw-r--r--js/src/threading/Mutex.h128
-rw-r--r--js/src/threading/Thread.h243
-rw-r--r--js/src/threading/posix/ConditionVariable.cpp180
-rw-r--r--js/src/threading/posix/MutexImpl.cpp81
-rw-r--r--js/src/threading/posix/MutexPlatformData.h19
-rw-r--r--js/src/threading/posix/Thread.cpp182
-rw-r--r--js/src/threading/windows/ConditionVariable.cpp418
-rw-r--r--js/src/threading/windows/MutexImpl.cpp85
-rw-r--r--js/src/threading/windows/MutexPlatformData.h19
-rw-r--r--js/src/threading/windows/Thread.cpp164
14 files changed, 1963 insertions, 0 deletions
diff --git a/js/src/threading/ConditionVariable.h b/js/src/threading/ConditionVariable.h
new file mode 100644
index 000000000..f1c9a906c
--- /dev/null
+++ b/js/src/threading/ConditionVariable.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef threading_ConditionVariable_h
+#define threading_ConditionVariable_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Move.h"
+#include "mozilla/TimeStamp.h"
+
+#include <stdint.h>
+#ifndef XP_WIN
+# include <pthread.h>
+#endif
+
+#include "threading/LockGuard.h"
+#include "threading/Mutex.h"
+
+namespace js {
+
+template <typename T> using UniqueLock = LockGuard<T>;
+
+enum class CVStatus {
+ NoTimeout,
+ Timeout
+};
+
+// A poly-fill for std::condition_variable.
+class ConditionVariable
+{
+public:
+ struct PlatformData;
+
+ ConditionVariable();
+ ~ConditionVariable();
+
+ // Wake one thread that is waiting on this condition.
+ void notify_one();
+
+ // Wake all threads that are waiting on this condition.
+ void notify_all();
+
+ // Block the current thread of execution until this condition variable is
+ // woken from another thread via notify_one or notify_all.
+ void wait(UniqueLock<Mutex>& lock);
+
+ // As with |wait|, block the current thread of execution until woken from
+ // another thread. This method will resume waiting once woken until the given
+ // Predicate |pred| evaluates to true.
+ template <typename Predicate>
+ void wait(UniqueLock<Mutex>& lock, Predicate pred) {
+ while (!pred()) {
+ wait(lock);
+ }
+ }
+
+ // Block the current thread of execution until woken from another thread, or
+ // the given absolute time is reached. The given absolute time is evaluated
+ // when this method is called, so will wake up after (abs_time - now),
+ // independent of system clock changes. While insulated from clock changes,
+ // this API is succeptible to the issues discussed above wait_for.
+ CVStatus wait_until(UniqueLock<Mutex>& lock,
+ const mozilla::TimeStamp& abs_time);
+
+ // As with |wait_until|, block the current thread of execution until woken
+ // from another thread, or the given absolute time is reached. This method
+ // will resume waiting once woken until the given Predicate |pred| evaluates
+ // to true.
+ template <typename Predicate>
+ bool wait_until(UniqueLock<Mutex>& lock, const mozilla::TimeStamp& abs_time,
+ Predicate pred) {
+ while (!pred()) {
+ if (wait_until(lock, abs_time) == CVStatus::Timeout) {
+ return pred();
+ }
+ }
+ return true;
+ }
+
+ // Block the current thread of execution until woken from another thread, or
+ // the given time duration has elapsed. Given that the system may be
+ // interrupted between the callee and the actual wait beginning, this call
+ // has a minimum granularity of the system's scheduling interval, and may
+ // encounter substantially longer delays, depending on system load.
+ CVStatus wait_for(UniqueLock<Mutex>& lock,
+ const mozilla::TimeDuration& rel_time);
+
+ // As with |wait_for|, block the current thread of execution until woken from
+ // another thread or the given time duration has elapsed. This method will
+ // resume waiting once woken until the given Predicate |pred| evaluates to
+ // true.
+ template <typename Predicate>
+ bool wait_for(UniqueLock<Mutex>& lock, const mozilla::TimeDuration& rel_time,
+ Predicate pred) {
+ return wait_until(lock, mozilla::TimeStamp::Now() + rel_time,
+ mozilla::Move(pred));
+ }
+
+
+private:
+ ConditionVariable(const ConditionVariable&) = delete;
+ ConditionVariable& operator=(const ConditionVariable&) = delete;
+
+ PlatformData* platformData();
+
+#ifndef XP_WIN
+ void* platformData_[sizeof(pthread_cond_t) / sizeof(void*)];
+ static_assert(sizeof(pthread_cond_t) / sizeof(void*) != 0 &&
+ sizeof(pthread_cond_t) % sizeof(void*) == 0,
+ "pthread_cond_t must have pointer alignment");
+#else
+ void* platformData_[4];
+#endif
+};
+
+} // namespace js
+
+#endif // threading_ConditionVariable_h
diff --git a/js/src/threading/ExclusiveData.h b/js/src/threading/ExclusiveData.h
new file mode 100644
index 000000000..732ef91d6
--- /dev/null
+++ b/js/src/threading/ExclusiveData.h
@@ -0,0 +1,197 @@
+/* -*- 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/. */
+
+#ifndef threading_ExclusiveData_h
+#define threading_ExclusiveData_h
+
+#include "mozilla/Alignment.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Move.h"
+
+#include "threading/Mutex.h"
+
+namespace js {
+
+/**
+ * A mutual exclusion lock class.
+ *
+ * `ExclusiveData` provides an RAII guard to automatically lock and unlock when
+ * accessing the protected inner value.
+ *
+ * Unlike the STL's `std::mutex`, the protected value is internal to this
+ * class. This is a huge win: one no longer has to rely on documentation to
+ * explain the relationship between a lock and its protected data, and the type
+ * system can enforce[0] it.
+ *
+ * For example, suppose we have a counter class:
+ *
+ * class Counter
+ * {
+ * int32_t i;
+ *
+ * public:
+ * void inc(int32_t n) { i += n; }
+ * };
+ *
+ * If we share a counter across threads with `std::mutex`, we rely solely on
+ * comments to document the relationship between the lock and its data, like
+ * this:
+ *
+ * class SharedCounter
+ * {
+ * // Remember to acquire `counter_lock` when accessing `counter`,
+ * // pretty please!
+ * Counter counter;
+ * std::mutex counter_lock;
+ *
+ * public:
+ * void inc(size_t n) {
+ * // Whoops, forgot to acquire the lock! Off to the races!
+ * counter.inc(n);
+ * }
+ * };
+ *
+ * In contrast, `ExclusiveData` wraps the protected value, enabling the type
+ * system to enforce that we acquire the lock before accessing the value:
+ *
+ * class SharedCounter
+ * {
+ * ExclusiveData<Counter> counter;
+ *
+ * public:
+ * void inc(size_t n) {
+ * auto guard = counter.lock();
+ * guard->inc(n);
+ * }
+ * };
+ *
+ * The API design is based on Rust's `std::sync::Mutex<T>` type.
+ *
+ * [0]: Of course, we don't have a borrow checker in C++, so the type system
+ * cannot guarantee that you don't stash references received from
+ * `ExclusiveData<T>::Guard` somewhere such that the reference outlives the
+ * guard's lifetime and therefore becomes invalid. To help avoid this last
+ * foot-gun, prefer using the guard directly! Do not store raw references
+ * to the protected value in other structures!
+ */
+template <typename T>
+class ExclusiveData
+{
+ mutable Mutex lock_;
+ mutable mozilla::AlignedStorage2<T> value_;
+
+ ExclusiveData(const ExclusiveData&) = delete;
+ ExclusiveData& operator=(const ExclusiveData&) = delete;
+
+ void acquire() const { lock_.lock(); }
+ void release() const { lock_.unlock(); }
+
+ public:
+ /**
+ * Create a new `ExclusiveData`, with perfect forwarding of the protected
+ * value.
+ */
+ template <typename U>
+ explicit ExclusiveData(const MutexId& id, U&& u)
+ : lock_(id)
+ {
+ new (value_.addr()) T(mozilla::Forward<U>(u));
+ }
+
+ /**
+ * Create a new `ExclusiveData`, constructing the protected value in place.
+ */
+ template <typename... Args>
+ explicit ExclusiveData(const MutexId& id, Args&&... args)
+ : lock_(id)
+ {
+ new (value_.addr()) T(mozilla::Forward<Args>(args)...);
+ }
+
+ ~ExclusiveData() {
+ acquire();
+ value_.addr()->~T();
+ release();
+ }
+
+ ExclusiveData(ExclusiveData&& rhs) :
+ lock_(mozilla::Move(rhs.lock))
+ {
+ MOZ_ASSERT(&rhs != this, "self-move disallowed!");
+ new (value_.addr()) T(mozilla::Move(*rhs.value_.addr()));
+ }
+
+ ExclusiveData& operator=(ExclusiveData&& rhs) {
+ this->~ExclusiveData();
+ new (this) ExclusiveData(mozilla::Move(rhs));
+ return *this;
+ }
+
+ /**
+ * An RAII class that provides exclusive access to a `ExclusiveData<T>`'s
+ * protected inner `T` value.
+ *
+ * Note that this is intentionally marked MOZ_STACK_CLASS instead of
+ * MOZ_RAII_CLASS, as the latter disallows moves and returning by value, but
+ * Guard utilizes both.
+ */
+ class MOZ_STACK_CLASS Guard
+ {
+ const ExclusiveData* parent_;
+
+ Guard(const Guard&) = delete;
+ Guard& operator=(const Guard&) = delete;
+
+ public:
+ explicit Guard(const ExclusiveData& parent)
+ : parent_(&parent)
+ {
+ parent_->acquire();
+ }
+
+ Guard(Guard&& rhs)
+ : parent_(rhs.parent_)
+ {
+ MOZ_ASSERT(&rhs != this, "self-move disallowed!");
+ rhs.parent_ = nullptr;
+ }
+
+ Guard& operator=(Guard&& rhs) {
+ this->~Guard();
+ new (this) Guard(mozilla::Move(rhs));
+ return *this;
+ }
+
+ T& get() const {
+ MOZ_ASSERT(parent_);
+ return *parent_->value_.addr();
+ }
+
+ operator T& () const { return get(); }
+ T* operator->() const { return &get(); }
+
+ const ExclusiveData<T>* parent() const {
+ MOZ_ASSERT(parent_);
+ return parent_;
+ }
+
+ ~Guard() {
+ if (parent_)
+ parent_->release();
+ }
+ };
+
+ /**
+ * Access the protected inner `T` value for exclusive reading and writing.
+ */
+ Guard lock() const {
+ return Guard(*this);
+ }
+};
+
+} // namespace js
+
+#endif // threading_ExclusiveData_h
diff --git a/js/src/threading/LockGuard.h b/js/src/threading/LockGuard.h
new file mode 100644
index 000000000..45b023a90
--- /dev/null
+++ b/js/src/threading/LockGuard.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef threading_LockGuard_h
+#define threading_LockGuard_h
+
+namespace js {
+
+template <typename Mutex> class MOZ_RAII UnlockGuard;
+
+template <typename Mutex>
+class MOZ_RAII LockGuard
+{
+ friend class UnlockGuard<Mutex>;
+ friend class ConditionVariable;
+ Mutex& lock;
+
+public:
+ explicit LockGuard(Mutex& aLock)
+ : lock(aLock)
+ {
+ lock.lock();
+ }
+
+ ~LockGuard() {
+ lock.unlock();
+ }
+};
+
+template <typename Mutex>
+class MOZ_RAII UnlockGuard
+{
+ Mutex& lock;
+
+public:
+ explicit UnlockGuard(LockGuard<Mutex>& aGuard)
+ : lock(aGuard.lock)
+ {
+ lock.unlock();
+ }
+
+ ~UnlockGuard() {
+ lock.lock();
+ }
+};
+
+} // namespace js
+
+#endif // threading_LockGuard_h
diff --git a/js/src/threading/Mutex.cpp b/js/src/threading/Mutex.cpp
new file mode 100644
index 000000000..154129dee
--- /dev/null
+++ b/js/src/threading/Mutex.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "threading/Mutex.h"
+
+#include "js/Utility.h"
+
+using namespace js;
+
+#ifdef DEBUG
+
+MOZ_THREAD_LOCAL(js::Mutex::MutexVector*) js::Mutex::HeldMutexStack;
+
+/* static */ bool
+js::Mutex::Init()
+{
+ return HeldMutexStack.init();
+}
+
+/* static */ void
+js::Mutex::ShutDown()
+{
+ js_delete(HeldMutexStack.get());
+ HeldMutexStack.set(nullptr);
+}
+
+/* static */ js::Mutex::MutexVector&
+js::Mutex::heldMutexStack()
+{
+ auto stack = HeldMutexStack.get();
+ if (!stack) {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ stack = js_new<MutexVector>();
+ if (!stack)
+ oomUnsafe.crash("js::Mutex::heldMutexStack");
+ HeldMutexStack.set(stack);
+ }
+ return *stack;
+}
+
+void
+js::Mutex::lock()
+{
+ auto& stack = heldMutexStack();
+ if (!stack.empty()) {
+ const Mutex& prev = *stack.back();
+ if (id_.order <= prev.id_.order) {
+ fprintf(stderr,
+ "Attempt to acquire mutex %s with order %d while holding %s with order %d\n",
+ id_.name, id_.order, prev.id_.name, prev.id_.order);
+ MOZ_CRASH("Mutex ordering violation");
+ }
+ }
+
+ MutexImpl::lock();
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!stack.append(this))
+ oomUnsafe.crash("js::Mutex::lock");
+}
+
+void
+js::Mutex::unlock()
+{
+ auto& stack = heldMutexStack();
+ MOZ_ASSERT(stack.back() == this);
+ MutexImpl::unlock();
+ stack.popBack();
+}
+
+#endif
diff --git a/js/src/threading/Mutex.h b/js/src/threading/Mutex.h
new file mode 100644
index 000000000..1ea019dde
--- /dev/null
+++ b/js/src/threading/Mutex.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef threading_Mutex_h
+#define threading_Mutex_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Move.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/Vector.h"
+
+#include <new>
+#include <string.h>
+
+namespace js {
+
+class ConditionVariable;
+
+namespace detail {
+
+class MutexImpl
+{
+public:
+ struct PlatformData;
+
+ MutexImpl();
+ ~MutexImpl();
+
+ MutexImpl(MutexImpl&& rhs)
+ : platformData_(rhs.platformData_)
+ {
+ MOZ_ASSERT(this != &rhs, "self move disallowed!");
+ rhs.platformData_ = nullptr;
+ }
+
+ MutexImpl& operator=(MutexImpl&& rhs) {
+ this->~MutexImpl();
+ new (this) MutexImpl(mozilla::Move(rhs));
+ return *this;
+ }
+
+ bool operator==(const MutexImpl& rhs) {
+ return platformData_ == rhs.platformData_;
+ }
+
+protected:
+ void lock();
+ void unlock();
+
+private:
+ MutexImpl(const MutexImpl&) = delete;
+ void operator=(const MutexImpl&) = delete;
+
+ friend class js::ConditionVariable;
+ PlatformData* platformData() {
+ MOZ_ASSERT(platformData_);
+ return platformData_;
+ };
+
+ PlatformData* platformData_;
+};
+
+} // namespace detail
+
+// A MutexId secifies the name and mutex order for a mutex.
+//
+// The mutex order defines the allowed order of mutex acqusition on a single
+// thread. Mutexes must be acquired in strictly increasing order. Mutexes with
+// the same order may not be held at the same time by that thread.
+struct MutexId
+{
+ const char* name;
+ uint32_t order;
+};
+
+#ifndef DEBUG
+
+class Mutex : public detail::MutexImpl
+{
+public:
+ static bool Init() { return true; }
+ static void ShutDown() {}
+
+ explicit Mutex(const MutexId& id) {}
+
+ using MutexImpl::lock;
+ using MutexImpl::unlock;
+};
+
+#else
+
+// In debug builds, js::Mutex is a wrapper over MutexImpl that checks correct
+// locking order is observed.
+//
+// The class maintains a per-thread stack of currently-held mutexes to enable it
+// to check this.
+class Mutex : public detail::MutexImpl
+{
+public:
+ static bool Init();
+ static void ShutDown();
+
+ explicit Mutex(const MutexId& id)
+ : id_(id)
+ {
+ MOZ_ASSERT(id_.order != 0);
+ }
+
+ void lock();
+ void unlock();
+
+private:
+ const MutexId id_;
+
+ using MutexVector = mozilla::Vector<const Mutex*>;
+ static MOZ_THREAD_LOCAL(MutexVector*) HeldMutexStack;
+ static MutexVector& heldMutexStack();
+};
+
+#endif
+
+} // namespace js
+
+#endif // threading_Mutex_h
diff --git a/js/src/threading/Thread.h b/js/src/threading/Thread.h
new file mode 100644
index 000000000..2ea445e7d
--- /dev/null
+++ b/js/src/threading/Thread.h
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef threading_Thread_h
+#define threading_Thread_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/IndexSequence.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Tuple.h"
+
+#include <stdint.h>
+
+#include "js/Utility.h"
+
+#ifdef XP_WIN
+# define THREAD_RETURN_TYPE unsigned int
+# define THREAD_CALL_API __stdcall
+#else
+# define THREAD_RETURN_TYPE void*
+# define THREAD_CALL_API
+#endif
+
+namespace js {
+namespace detail {
+template <typename F, typename... Args>
+class ThreadTrampoline;
+} // namespace detail
+
+// Execute the given functor concurrent with the currently executing instruction
+// stream and within the current address space. Use with care.
+class Thread
+{
+public:
+ struct Hasher;
+
+ class Id
+ {
+ friend struct Hasher;
+ class PlatformData;
+ void* platformData_[2];
+
+ public:
+ Id();
+
+ Id(const Id&) = default;
+ Id(Id&&) = default;
+ Id& operator=(const Id&) = default;
+ Id& operator=(Id&&) = default;
+
+ bool operator==(const Id& aOther) const;
+ bool operator!=(const Id& aOther) const { return !operator==(aOther); }
+
+ inline PlatformData* platformData();
+ inline const PlatformData* platformData() const;
+ };
+
+ // Provides optional parameters to a Thread.
+ class Options
+ {
+ size_t stackSize_;
+
+ public:
+ Options() : stackSize_(0) {}
+
+ Options& setStackSize(size_t sz) { stackSize_ = sz; return *this; }
+ size_t stackSize() const { return stackSize_; }
+ };
+
+ // A js::HashTable hash policy for keying hash tables by js::Thread::Id.
+ struct Hasher
+ {
+ typedef Id Lookup;
+
+ static HashNumber hash(const Lookup& l);
+
+ static bool match(const Id& key, const Lookup& lookup) {
+ return key == lookup;
+ }
+ };
+
+ // Create a Thread in an initially unjoinable state. A thread of execution can
+ // be created for this Thread by calling |init|. Some of the thread's
+ // properties may be controlled by passing options to this constructor.
+ template <typename O = Options,
+ // SFINAE to make sure we don't try and treat functors for the other
+ // constructor as an Options and vice versa.
+ typename NonConstO = typename mozilla::RemoveConst<O>::Type,
+ typename DerefO = typename mozilla::RemoveReference<NonConstO>::Type,
+ typename = typename mozilla::EnableIf<mozilla::IsSame<DerefO, Options>::value,
+ void*>::Type>
+ explicit Thread(O&& options = Options())
+ : id_(Id())
+ , options_(mozilla::Forward<O>(options))
+ { }
+
+ // Start a thread of execution at functor |f| with parameters |args|. This
+ // method will return false if thread creation fails. This Thread must not
+ // already have been created. Note that the arguments must be either POD or
+ // rvalue references (mozilla::Move). Attempting to pass a reference will
+ // result in the value being copied, which may not be the intended behavior.
+ // See the comment below on ThreadTrampoline::args for an explanation.
+ template <typename F, typename... Args>
+ MOZ_MUST_USE bool init(F&& f, Args&&... args) {
+ MOZ_RELEASE_ASSERT(!joinable());
+ using Trampoline = detail::ThreadTrampoline<F, Args...>;
+ AutoEnterOOMUnsafeRegion oom;
+ auto trampoline = js_new<Trampoline>(mozilla::Forward<F>(f),
+ mozilla::Forward<Args>(args)...);
+ if (!trampoline)
+ oom.crash("js::Thread::init");
+ return create(Trampoline::Start, trampoline);
+ }
+
+ // The thread must be joined or detached before destruction.
+ ~Thread() {
+ MOZ_RELEASE_ASSERT(!joinable());
+ }
+
+ // Move the thread into the detached state without blocking. In the detatched
+ // state, the thread continues to run until it exits, but cannot be joined.
+ // After this method returns, this Thread no longer represents a thread of
+ // execution. When the thread exits, its resources will be cleaned up by the
+ // system. At process exit, if the thread is still running, the thread's TLS
+ // storage will be destructed, but the thread stack will *not* be unrolled.
+ void detach();
+
+ // Block the current thread until this Thread returns from the functor it was
+ // created with. The thread's resources will be cleaned up before this
+ // function returns. After this method returns, this Thread no longer
+ // represents a thread of execution.
+ void join();
+
+ // Return true if this thread has not yet been joined or detached. If this
+ // method returns false, this Thread does not have an associated thread of
+ // execution, for example, if it has been previously moved or joined.
+ bool joinable() const {
+ return get_id() != Id();
+ }
+
+ // Returns the id of this thread if this represents a thread of execution or
+ // the default constructed Id() if not. The thread ID is guaranteed to
+ // uniquely identify a thread and can be compared with the == operator.
+ Id get_id() const { return id_; }
+
+ // Allow threads to be moved so that they can be stored in containers.
+ Thread(Thread&& aOther);
+ Thread& operator=(Thread&& aOther);
+
+private:
+ // Disallow copy as that's not sensible for unique resources.
+ Thread(const Thread&) = delete;
+ void operator=(const Thread&) = delete;
+
+ // Provide a process global ID to each thread.
+ Id id_;
+
+ // Overridable thread creation options.
+ Options options_;
+
+ // Dispatch to per-platform implementation of thread creation.
+ MOZ_MUST_USE bool create(THREAD_RETURN_TYPE (THREAD_CALL_API *aMain)(void*), void* aArg);
+};
+
+namespace ThisThread {
+
+// Return the thread id of the calling thread.
+Thread::Id GetId();
+
+// Set the current thread name. Note that setting the thread name may not be
+// available on all platforms; on these platforms setName() will simply do
+// nothing.
+void SetName(const char* name);
+
+// Get the current thread name. As with SetName, not available on all
+// platforms. On these platforms getName() will give back an empty string (by
+// storing NUL in nameBuffer[0]). 'len' is the bytes available to be written in
+// 'nameBuffer', including the terminating NUL.
+void GetName(char* nameBuffer, size_t len);
+
+} // namespace ThisThread
+
+namespace detail {
+
+// Platform thread APIs allow passing a single void* argument to the target
+// thread. This class is responsible for safely ferrying the arg pack and
+// functor across that void* membrane and running it in the other thread.
+template <typename F, typename... Args>
+class ThreadTrampoline
+{
+ // The functor to call.
+ F f;
+
+ // A std::decay copy of the arguments, as specified by std::thread. Using an
+ // rvalue reference for the arguments to Thread and ThreadTrampoline gives us
+ // move semantics for large structures, allowing us to quickly and easily pass
+ // enormous amounts of data to a new thread. Unfortunately, there is a
+ // downside: rvalue references becomes lvalue references when used with POD
+ // types. This becomes dangerous when attempting to pass POD stored on the
+ // stack to the new thread; the rvalue reference will implicitly become an
+ // lvalue reference to the stack location. Thus, the value may not exist if
+ // the parent thread leaves the frame before the read happens in the new
+ // thread. To avoid this dangerous and highly non-obvious footgun, the
+ // standard requires a "decay" copy of the arguments at the cost of making it
+ // impossible to pass references between threads.
+ mozilla::Tuple<typename mozilla::Decay<Args>::Type...> args;
+
+public:
+ // Note that this template instatiation duplicates and is identical to the
+ // class template instantiation. It is required for perfect forwarding of
+ // rvalue references, which is only enabled for calls to a function template,
+ // even if the class template arguments are correct.
+ template <typename G, typename... ArgsT>
+ explicit ThreadTrampoline(G&& aG, ArgsT&&... aArgsT)
+ : f(mozilla::Forward<F>(aG)),
+ args(mozilla::Forward<Args>(aArgsT)...)
+ {
+ }
+
+ static THREAD_RETURN_TYPE THREAD_CALL_API Start(void* aPack) {
+ auto* pack = static_cast<ThreadTrampoline<F, Args...>*>(aPack);
+ pack->callMain(typename mozilla::IndexSequenceFor<Args...>::Type());
+ js_delete(pack);
+ return 0;
+ }
+
+ template<size_t ...Indices>
+ void callMain(mozilla::IndexSequence<Indices...>) {
+ f(mozilla::Get<Indices>(args)...);
+ }
+};
+
+} // namespace detail
+} // namespace js
+
+#undef THREAD_RETURN_TYPE
+
+#endif // threading_Thread_h
diff --git a/js/src/threading/posix/ConditionVariable.cpp b/js/src/threading/posix/ConditionVariable.cpp
new file mode 100644
index 000000000..35a90c604
--- /dev/null
+++ b/js/src/threading/posix/ConditionVariable.cpp
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "threading/ConditionVariable.h"
+#include "threading/Mutex.h"
+#include "threading/posix/MutexPlatformData.h"
+
+using mozilla::CheckedInt;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+static const long NanoSecPerSec = 1000000000;
+
+// Android 32-bit & macOS 10.12 has the clock functions, but not pthread_condattr_setclock.
+#if defined(HAVE_CLOCK_MONOTONIC) && \
+ !(defined(__ANDROID__) && !defined(__LP64__)) && !defined(__APPLE__)
+# define USE_CLOCK_API
+#endif
+
+#ifdef USE_CLOCK_API
+// The C++ specification defines std::condition_variable::wait_for in terms of
+// std::chrono::steady_clock, which is closest to CLOCK_MONOTONIC.
+static const clockid_t WhichClock = CLOCK_MONOTONIC;
+
+// While timevaladd is widely available to work with timevals, the newer
+// timespec structure is largely lacking such conveniences. Thankfully, the
+// utilities available in MFBT make implementing our own quite easy.
+static void
+moz_timespecadd(struct timespec* lhs, struct timespec* rhs, struct timespec* result)
+{
+ // Add nanoseconds. This may wrap, but not above 2 billion.
+ MOZ_RELEASE_ASSERT(lhs->tv_nsec < NanoSecPerSec);
+ MOZ_RELEASE_ASSERT(rhs->tv_nsec < NanoSecPerSec);
+ result->tv_nsec = lhs->tv_nsec + rhs->tv_nsec;
+
+ // Add seconds, checking for overflow in the platform specific time_t type.
+ CheckedInt<time_t> sec = CheckedInt<time_t>(lhs->tv_sec) + rhs->tv_sec;
+
+ // If nanoseconds overflowed, carry the result over into seconds.
+ if (result->tv_nsec >= NanoSecPerSec) {
+ MOZ_RELEASE_ASSERT(result->tv_nsec < 2 * NanoSecPerSec);
+ result->tv_nsec -= NanoSecPerSec;
+ sec += 1;
+ }
+
+ // Extracting the value asserts that there was no overflow.
+ MOZ_RELEASE_ASSERT(sec.isValid());
+ result->tv_sec = sec.value();
+}
+#endif
+
+struct js::ConditionVariable::PlatformData
+{
+ pthread_cond_t ptCond;
+};
+
+js::ConditionVariable::ConditionVariable()
+{
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+
+#ifdef USE_CLOCK_API
+ pthread_condattr_t attr;
+ int r0 = pthread_condattr_init(&attr);
+ MOZ_RELEASE_ASSERT(!r0);
+
+ int r1 = pthread_condattr_setclock(&attr, WhichClock);
+ MOZ_RELEASE_ASSERT(!r1);
+
+ int r2 = pthread_cond_init(ptCond, &attr);
+ MOZ_RELEASE_ASSERT(!r2);
+
+ int r3 = pthread_condattr_destroy(&attr);
+ MOZ_RELEASE_ASSERT(!r3);
+#else
+ int r = pthread_cond_init(ptCond, NULL);
+ MOZ_RELEASE_ASSERT(!r);
+#endif
+}
+
+js::ConditionVariable::~ConditionVariable()
+{
+ int r = pthread_cond_destroy(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void
+js::ConditionVariable::notify_one()
+{
+ int r = pthread_cond_signal(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void
+js::ConditionVariable::notify_all()
+{
+ int r = pthread_cond_broadcast(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void
+js::ConditionVariable::wait(UniqueLock<Mutex>& lock)
+{
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+ pthread_mutex_t* ptMutex = &lock.lock.platformData()->ptMutex;
+
+ int r = pthread_cond_wait(ptCond, ptMutex);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+js::CVStatus
+js::ConditionVariable::wait_until(UniqueLock<Mutex>& lock,
+ const TimeStamp& abs_time)
+{
+ return wait_for(lock, abs_time - TimeStamp::Now());
+}
+
+js::CVStatus
+js::ConditionVariable::wait_for(UniqueLock<Mutex>& lock,
+ const TimeDuration& a_rel_time)
+{
+ if (a_rel_time == TimeDuration::Forever()) {
+ wait(lock);
+ return CVStatus::NoTimeout;
+ }
+
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+ pthread_mutex_t* ptMutex = &lock.lock.platformData()->ptMutex;
+ int r;
+
+ // Clamp to 0, as time_t is unsigned.
+ TimeDuration rel_time = a_rel_time < TimeDuration::FromSeconds(0)
+ ? TimeDuration::FromSeconds(0)
+ : a_rel_time;
+
+ // Convert the duration to a timespec.
+ struct timespec rel_ts;
+ rel_ts.tv_sec = static_cast<time_t>(rel_time.ToSeconds());
+ rel_ts.tv_nsec = static_cast<uint64_t>(rel_time.ToMicroseconds() * 1000.0) % NanoSecPerSec;
+
+#ifdef USE_CLOCK_API
+ struct timespec now_ts;
+ r = clock_gettime(WhichClock, &now_ts);
+ MOZ_RELEASE_ASSERT(!r);
+
+ struct timespec abs_ts;
+ moz_timespecadd(&now_ts, &rel_ts, &abs_ts);
+
+ r = pthread_cond_timedwait(ptCond, ptMutex, &abs_ts);
+#else
+ // Our non-clock-supporting platforms, OS X and Android, do support waiting
+ // on a condition variable with a relative timeout.
+ r = pthread_cond_timedwait_relative_np(ptCond, ptMutex, &rel_ts);
+#endif
+
+ if (r == 0) {
+ return CVStatus::NoTimeout;
+ }
+ MOZ_RELEASE_ASSERT(r == ETIMEDOUT);
+ return CVStatus::Timeout;
+}
+
+js::ConditionVariable::PlatformData*
+js::ConditionVariable::platformData()
+{
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/js/src/threading/posix/MutexImpl.cpp b/js/src/threading/posix/MutexImpl.cpp
new file mode 100644
index 000000000..1d406400f
--- /dev/null
+++ b/js/src/threading/posix/MutexImpl.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+
+#include "js/Utility.h"
+
+#include "threading/Mutex.h"
+#include "threading/posix/MutexPlatformData.h"
+
+#define TRY_CALL_PTHREADS(call, msg) \
+ { \
+ int result = (call); \
+ if (result != 0) { \
+ errno = result; \
+ perror(msg); \
+ MOZ_CRASH(msg); \
+ } \
+ }
+
+js::detail::MutexImpl::MutexImpl()
+{
+ AutoEnterOOMUnsafeRegion oom;
+ platformData_ = js_new<PlatformData>();
+ if (!platformData_)
+ oom.crash("js::detail::MutexImpl::MutexImpl");
+
+ pthread_mutexattr_t* attrp = nullptr;
+
+#ifdef DEBUG
+ pthread_mutexattr_t attr;
+
+ TRY_CALL_PTHREADS(pthread_mutexattr_init(&attr),
+ "js::detail::MutexImpl::MutexImpl: pthread_mutexattr_init failed");
+
+ TRY_CALL_PTHREADS(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK),
+ "js::detail::MutexImpl::MutexImpl: pthread_mutexattr_settype failed");
+
+ attrp = &attr;
+#endif
+
+ TRY_CALL_PTHREADS(pthread_mutex_init(&platformData()->ptMutex, attrp),
+ "js::detail::MutexImpl::MutexImpl: pthread_mutex_init failed");
+
+#ifdef DEBUG
+ TRY_CALL_PTHREADS(pthread_mutexattr_destroy(&attr),
+ "js::detail::MutexImpl::MutexImpl: pthread_mutexattr_destroy failed");
+#endif
+}
+
+js::detail::MutexImpl::~MutexImpl()
+{
+ if (!platformData_)
+ return;
+
+ TRY_CALL_PTHREADS(pthread_mutex_destroy(&platformData()->ptMutex),
+ "js::detail::MutexImpl::~MutexImpl: pthread_mutex_destroy failed");
+
+ js_delete(platformData());
+}
+
+void
+js::detail::MutexImpl::lock()
+{
+ TRY_CALL_PTHREADS(pthread_mutex_lock(&platformData()->ptMutex),
+ "js::detail::MutexImpl::lock: pthread_mutex_lock failed");
+}
+
+void
+js::detail::MutexImpl::unlock()
+{
+ TRY_CALL_PTHREADS(pthread_mutex_unlock(&platformData()->ptMutex),
+ "js::detail::MutexImpl::unlock: pthread_mutex_unlock failed");
+}
+
+#undef TRY_CALL_PTHREADS
diff --git a/js/src/threading/posix/MutexPlatformData.h b/js/src/threading/posix/MutexPlatformData.h
new file mode 100644
index 000000000..487d89681
--- /dev/null
+++ b/js/src/threading/posix/MutexPlatformData.h
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+#ifndef platform_win_MutexPlatformData_h
+#define platform_win_MutexPlatformData_h
+
+#include <pthread.h>
+
+#include "threading/Mutex.h"
+
+struct js::detail::MutexImpl::PlatformData
+{
+ pthread_mutex_t ptMutex;
+};
+
+#endif // platform_win_MutexPlatformData_h
diff --git a/js/src/threading/posix/Thread.cpp b/js/src/threading/posix/Thread.cpp
new file mode 100644
index 000000000..2572cc727
--- /dev/null
+++ b/js/src/threading/posix/Thread.cpp
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/Assertions.h"
+
+#include <new>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(__APPLE__) && defined(__MACH__)
+#include <dlfcn.h>
+#endif
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+#include <pthread_np.h>
+#endif
+
+#if defined(__linux__)
+#include <sys/prctl.h>
+#endif
+
+#include "threading/Thread.h"
+
+class js::Thread::Id::PlatformData
+{
+ friend class js::Thread;
+ friend js::Thread::Id js::ThisThread::GetId();
+
+ pthread_t ptThread;
+
+ // pthread_t does not have a default initializer, so we have to carry a bool
+ // to tell whether it is safe to compare or not.
+ bool hasThread;
+};
+
+/* static */ js::HashNumber
+js::Thread::Hasher::hash(const Lookup& l)
+{
+ return mozilla::HashBytes(&l.platformData()->ptThread, sizeof(pthread_t));
+}
+
+inline js::Thread::Id::PlatformData*
+js::Thread::Id::platformData()
+{
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
+
+inline const js::Thread::Id::PlatformData*
+js::Thread::Id::platformData() const
+{
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<const PlatformData*>(platformData_);
+}
+
+js::Thread::Id::Id()
+{
+ platformData()->hasThread = false;
+}
+
+bool
+js::Thread::Id::operator==(const Id& aOther) const
+{
+ const PlatformData& self = *platformData();
+ const PlatformData& other = *aOther.platformData();
+ return (!self.hasThread && !other.hasThread) ||
+ (self.hasThread == other.hasThread &&
+ pthread_equal(self.ptThread, other.ptThread));
+}
+
+js::Thread::Thread(Thread&& aOther)
+{
+ id_ = aOther.id_;
+ aOther.id_ = Id();
+}
+
+js::Thread&
+js::Thread::operator=(Thread&& aOther)
+{
+ MOZ_RELEASE_ASSERT(!joinable());
+ id_ = aOther.id_;
+ aOther.id_ = Id();
+ return *this;
+}
+
+bool
+js::Thread::create(void* (*aMain)(void*), void* aArg)
+{
+ pthread_attr_t attrs;
+ int r = pthread_attr_init(&attrs);
+ MOZ_RELEASE_ASSERT(!r);
+ if (options_.stackSize()) {
+ r = pthread_attr_setstacksize(&attrs, options_.stackSize());
+ MOZ_RELEASE_ASSERT(!r);
+ }
+ r = pthread_create(&id_.platformData()->ptThread, &attrs, aMain, aArg);
+ if (r) {
+ // |pthread_create| may leave id_ in an undefined state.
+ id_ = Id();
+ return false;
+ }
+ id_.platformData()->hasThread = true;
+ return true;
+}
+
+void
+js::Thread::join()
+{
+ MOZ_RELEASE_ASSERT(joinable());
+ int r = pthread_join(id_.platformData()->ptThread, nullptr);
+ MOZ_RELEASE_ASSERT(!r);
+ id_ = Id();
+}
+
+void
+js::Thread::detach()
+{
+ MOZ_RELEASE_ASSERT(joinable());
+ int r = pthread_detach(id_.platformData()->ptThread);
+ MOZ_RELEASE_ASSERT(!r);
+ id_ = Id();
+}
+
+js::Thread::Id
+js::ThisThread::GetId()
+{
+ js::Thread::Id id;
+ id.platformData()->ptThread = pthread_self();
+ id.platformData()->hasThread = true;
+ return id;
+}
+
+void
+js::ThisThread::SetName(const char* name)
+{
+ MOZ_RELEASE_ASSERT(name);
+
+#if (defined(__APPLE__) && defined(__MACH__)) || defined(__linux__)
+ // On linux and OS X the name may not be longer than 16 bytes, including
+ // the null terminator. Truncate the name to 15 characters.
+ char nameBuf[16];
+
+ strncpy(nameBuf, name, sizeof nameBuf - 1);
+ nameBuf[sizeof nameBuf - 1] = '\0';
+ name = nameBuf;
+#endif
+
+ int rv;
+#ifdef XP_DARWIN
+ rv = pthread_setname_np(name);
+#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+ pthread_set_name_np(pthread_self(), name);
+ rv = 0;
+#elif defined(__NetBSD__)
+ rv = pthread_setname_np(pthread_self(), "%s", (void*)name);
+#else
+ rv = pthread_setname_np(pthread_self(), name);
+#endif
+ MOZ_RELEASE_ASSERT(!rv);
+}
+
+void
+js::ThisThread::GetName(char* nameBuffer, size_t len)
+{
+ MOZ_RELEASE_ASSERT(len >= 16);
+
+ int rv = -1;
+#ifdef HAVE_PTHREAD_GETNAME_NP
+ rv = pthread_getname_np(pthread_self(), nameBuffer, len);
+#elif defined(__linux__)
+ rv = prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(nameBuffer));
+#endif
+
+ if (rv)
+ nameBuffer[0] = '\0';
+}
diff --git a/js/src/threading/windows/ConditionVariable.cpp b/js/src/threading/windows/ConditionVariable.cpp
new file mode 100644
index 000000000..868c35141
--- /dev/null
+++ b/js/src/threading/windows/ConditionVariable.cpp
@@ -0,0 +1,418 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/Assertions.h"
+
+#include <float.h>
+#include <intrin.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include "threading/ConditionVariable.h"
+#include "threading/Mutex.h"
+#include "threading/windows/MutexPlatformData.h"
+
+// Some versions of the Windows SDK have a bug where some interlocked functions
+// are not redefined as compiler intrinsics. Fix that for the interlocked
+// functions that are used in this file.
+#if defined(_MSC_VER) && !defined(InterlockedExchangeAdd)
+#define InterlockedExchangeAdd(addend, value) \
+ _InterlockedExchangeAdd((volatile long*)(addend), (long)(value))
+#endif
+
+#if defined(_MSC_VER) && !defined(InterlockedIncrement)
+#define InterlockedIncrement(addend) \
+ _InterlockedIncrement((volatile long*)(addend))
+#endif
+
+// Windows XP and Server 2003 don't support condition variables natively. The
+// NativeImports class is responsible for detecting native support and
+// retrieving the appropriate function pointers. It gets instantiated once,
+// using a static initializer.
+class ConditionVariableNativeImports
+{
+public:
+ ConditionVariableNativeImports() {
+ HMODULE kernel32_dll = GetModuleHandle("kernel32.dll");
+ MOZ_RELEASE_ASSERT(kernel32_dll != NULL);
+
+#define LOAD_SYMBOL(symbol) loadSymbol(kernel32_dll, #symbol, symbol)
+ supported_ = LOAD_SYMBOL(InitializeConditionVariable) &&
+ LOAD_SYMBOL(WakeConditionVariable) &&
+ LOAD_SYMBOL(WakeAllConditionVariable) &&
+ LOAD_SYMBOL(SleepConditionVariableCS);
+#undef LOAD_SYMBOL
+ }
+
+ inline bool supported() const {
+ return supported_;
+ }
+
+ void(WINAPI* InitializeConditionVariable)(CONDITION_VARIABLE* ConditionVariable);
+ void(WINAPI* WakeAllConditionVariable)(PCONDITION_VARIABLE ConditionVariable);
+ void(WINAPI* WakeConditionVariable)(CONDITION_VARIABLE* ConditionVariable);
+ BOOL(WINAPI* SleepConditionVariableCS)(CONDITION_VARIABLE* ConditionVariable,
+ CRITICAL_SECTION* CriticalSection,
+ DWORD dwMilliseconds);
+
+private:
+ template <typename T>
+ inline bool loadSymbol(HMODULE module, const char* name, T& fn) {
+ FARPROC ptr = GetProcAddress(module, name);
+ if (!ptr)
+ return false;
+
+ fn = reinterpret_cast<T>(ptr);
+ return true;
+ }
+
+ bool supported_;
+};
+
+static ConditionVariableNativeImports sNativeImports;
+
+// Wrapper for native condition variable APIs.
+struct ConditionVariableNative
+{
+ inline void initialize() {
+ sNativeImports.InitializeConditionVariable(&cv_);
+ }
+
+ inline void destroy() {
+ // Native condition variables don't require cleanup.
+ }
+
+ inline void notify_one() { sNativeImports.WakeConditionVariable(&cv_); }
+
+ inline void notify_all() { sNativeImports.WakeAllConditionVariable(&cv_); }
+
+ inline bool wait(CRITICAL_SECTION* cs, DWORD msec) {
+ return sNativeImports.SleepConditionVariableCS(&cv_, cs, msec);
+ }
+
+private:
+ CONDITION_VARIABLE cv_;
+};
+
+// Fallback condition variable support for Windows XP and Server 2003. Given the
+// difficulty of testing on these antiquated platforms and their rapidly
+// diminishing market share, this implementation trades performance for
+// predictable behavior.
+struct ConditionVariableFallback
+{
+ static const uint32_t WAKEUP_MODE_NONE = 0;
+ static const uint32_t WAKEUP_MODE_ONE = 0x40000000;
+ static const uint32_t WAKEUP_MODE_ALL = 0x80000000;
+
+ static const uint32_t WAKEUP_MODE_MASK = WAKEUP_MODE_ONE | WAKEUP_MODE_ALL;
+ static const uint32_t SLEEPERS_COUNT_MASK = ~WAKEUP_MODE_MASK;
+
+ void initialize()
+ {
+ // Initialize the state variable to 0 sleepers, no wakeup.
+ sleepersCountAndWakeupMode_ = 0 | WAKEUP_MODE_NONE;
+
+ // Create a semaphore that prevents threads from entering sleep,
+ // or waking other threads while a wakeup is ongoing.
+ sleepWakeupSemaphore_ = CreateSemaphoreW(NULL, 1, 1, NULL);
+ MOZ_RELEASE_ASSERT(sleepWakeupSemaphore_);
+
+ // Use an auto-reset event for waking up a single sleeper.
+ wakeOneEvent_ = CreateEventW(NULL, FALSE, FALSE, NULL);
+ MOZ_RELEASE_ASSERT(wakeOneEvent_);
+
+ // Use a manual-reset event for waking up all sleepers.
+ wakeAllEvent_ = CreateEventW(NULL, TRUE, FALSE, NULL);
+ MOZ_RELEASE_ASSERT(wakeAllEvent_);
+ }
+
+ void destroy()
+ {
+ BOOL r;
+
+ MOZ_RELEASE_ASSERT(sleepersCountAndWakeupMode_ == (0 | WAKEUP_MODE_NONE));
+
+ r = CloseHandle(sleepWakeupSemaphore_);
+ MOZ_RELEASE_ASSERT(r);
+
+ r = CloseHandle(wakeOneEvent_);
+ MOZ_RELEASE_ASSERT(r);
+
+ r = CloseHandle(wakeAllEvent_);
+ MOZ_RELEASE_ASSERT(r);
+ }
+
+private:
+ void wakeup(uint32_t wakeupMode, HANDLE wakeEvent)
+ {
+ // Ensure that only one thread at a time can wake up others.
+ BOOL result = WaitForSingleObject(sleepWakeupSemaphore_, INFINITE);
+ MOZ_RELEASE_ASSERT(result == WAIT_OBJECT_0);
+
+ // Atomically set the wakeup mode and retrieve the number of sleepers.
+ uint32_t wcwm = InterlockedExchangeAdd(&sleepersCountAndWakeupMode_,
+ wakeupMode);
+ uint32_t sleepersCount = wcwm & SLEEPERS_COUNT_MASK;
+ MOZ_RELEASE_ASSERT((wcwm & WAKEUP_MODE_MASK) == WAKEUP_MODE_NONE);
+
+ if (sleepersCount > 0) {
+ // If there are any sleepers, set the wake event. The (last) woken
+ // up thread is responsible for releasing the semaphore.
+ BOOL success = SetEvent(wakeEvent);
+ MOZ_RELEASE_ASSERT(success);
+
+ } else {
+ // If there are no sleepers, set the wakeup mode back to 'none'
+ // and release the semaphore ourselves.
+ sleepersCountAndWakeupMode_ = 0 | WAKEUP_MODE_NONE;
+
+ BOOL success = ReleaseSemaphore(sleepWakeupSemaphore_, 1, NULL);
+ MOZ_RELEASE_ASSERT(success);
+ }
+ }
+
+public:
+ void notify_one() { wakeup(WAKEUP_MODE_ONE, wakeOneEvent_); }
+
+ void notify_all() { wakeup(WAKEUP_MODE_ALL, wakeAllEvent_); }
+
+ bool wait(CRITICAL_SECTION* userLock, DWORD msec)
+ {
+ // Make sure that we can't enter sleep when there are other threads
+ // that still need to wake up on either of the wake events being set.
+ DWORD result = WaitForSingleObject(sleepWakeupSemaphore_, INFINITE);
+ MOZ_RELEASE_ASSERT(result == WAIT_OBJECT_0);
+
+ // Register ourselves as a sleeper. Use an atomic operation, because
+ // if another thread times out at the same time, it will decrement the
+ // sleepers count without acquiring the semaphore.
+ uint32_t wcwm = InterlockedIncrement(&sleepersCountAndWakeupMode_);
+ MOZ_RELEASE_ASSERT((wcwm & WAKEUP_MODE_MASK) == WAKEUP_MODE_NONE);
+
+ // Now that that this thread has been enlisted as a sleeper, it is safe
+ // again for other threads to do a wakeup.
+ BOOL success = ReleaseSemaphore(sleepWakeupSemaphore_, 1, NULL);
+ MOZ_RELEASE_ASSERT(success);
+
+ // Release the caller's mutex.
+ LeaveCriticalSection(userLock);
+
+ // Wait for either event to become signaled, which happens when
+ // notify_one() or notify_all() is called, or for a timeout.
+ HANDLE handles[2] = { wakeOneEvent_, wakeAllEvent_ };
+ DWORD waitResult = WaitForMultipleObjects(2, handles, FALSE, msec);
+ MOZ_RELEASE_ASSERT(waitResult == WAIT_OBJECT_0 ||
+ waitResult == WAIT_OBJECT_0 + 1 ||
+ (waitResult == WAIT_TIMEOUT && msec != INFINITE));
+
+ // Atomically decrease the sleepers count and retrieve the wakeup mode
+ // and new sleepers count.
+ // If the wait returned because wakeOneEvent_ was set, we are certain
+ // that the wakeup mode will be WAKEUP_MODE_ONE. In that case,
+ // atomically reset the wakeup mode to 'none', because if another
+ // thread's sleep times out at same time and it finds that it was the
+ // last sleeper, it decides whether or not to reset the wakeOneEvent_
+ // based on the current wakeup mode.
+ uint32_t sub;
+ if (waitResult == WAIT_OBJECT_0)
+ sub = 1 | WAKEUP_MODE_ONE;
+ else
+ sub = 1;
+ // Note that InterlockedExchangeAdd returns the old value, but it's
+ // easier to work with the new value.
+ wcwm = InterlockedExchangeAdd(&sleepersCountAndWakeupMode_, -sub) - sub;
+
+ uint32_t wakeupMode = wcwm & WAKEUP_MODE_MASK;
+ uint32_t sleepersCount = wcwm & SLEEPERS_COUNT_MASK;
+
+ bool releaseSleepWakeupSemaphore = false;
+
+ if (waitResult == WAIT_OBJECT_0) {
+ // The wake-one event is an auto-reset event so if we're woken by
+ // it, it should already have been reset. We also already removed
+ // the WAKEUP_MODE_ONE bit so the wakeup mode should now be 'none'
+ // again.
+ MOZ_RELEASE_ASSERT(wakeupMode == WAKEUP_MODE_NONE);
+
+ // The signaling thread has acquired the enter-wakeup semaphore and
+ // expects the woken (this) thread to release it again.
+ releaseSleepWakeupSemaphore = true;
+
+ } else if (waitResult == WAIT_TIMEOUT && wakeupMode == WAKEUP_MODE_ONE &&
+ sleepersCount == 0) {
+ // In theory a race condition is possible where the last sleeper
+ // times out right at the moment that another thread signals it.
+ // If that just happened we now have a dangling signal event and
+ // mode, but no threads to be woken up by it, and we need to clean
+ // that up.
+ BOOL success = ResetEvent(wakeOneEvent_);
+ MOZ_RELEASE_ASSERT(success);
+
+ // This is safe - we are certain there are no other sleepers that
+ // could wake up right now, and the semaphore ensures that no
+ // non-sleeping threads are messing with
+ // sleepersCountAndWakeupMode_.
+ sleepersCountAndWakeupMode_ = 0 | WAKEUP_MODE_NONE;
+
+ // The signaling thread has acquired the sleep-wakeup semaphore and
+ // expects the woken thread to release it. But since there are no
+ // sleeping threads left this thread will do it instead.
+ releaseSleepWakeupSemaphore = true;
+
+ } else if (wakeupMode == WAKEUP_MODE_ALL && sleepersCount == 0) {
+ // If this was the last thread waking up in response to a
+ // notify_all, clear the wakeup mode and reset the wake-all event.
+ // A race condition similar to the case described above could
+ // occur, so waitResult could be WAIT_TIMEOUT, but that doesn't
+ // matter for the actions that need to be taken.
+ MOZ_RELEASE_ASSERT(waitResult = WAIT_OBJECT_0 + 1 ||
+ waitResult == WAIT_TIMEOUT);
+
+ BOOL success = ResetEvent(wakeAllEvent_);
+ MOZ_RELEASE_ASSERT(success);
+
+ sleepersCountAndWakeupMode_ = 0 | WAKEUP_MODE_NONE;
+
+ // The broadcasting thread has acquired the enter-wakeup semaphore
+ // and expects the last thread that wakes up to release it.
+ releaseSleepWakeupSemaphore = true;
+
+ } else if ((waitResult == WAIT_TIMEOUT && msec != INFINITE) ||
+ (waitResult == WAIT_OBJECT_0 + 1 &&
+ wakeupMode == WAKEUP_MODE_ALL)) {
+ // Either:
+ // * The wait timed out but found no active notify_one or notify_all
+ // the moment it decreased the wait count.
+ // * A notify_all woke up this thread but there are more threads
+ // that need to be woken up by the wake-all event.
+ // These are ordinary conditions in which we don't have to do
+ // anything.
+
+ } else {
+ MOZ_CRASH("invalid wakeup condition");
+ }
+
+ // Release the enter-wakeup semaphore if the wakeup condition requires
+ // us to do it.
+ if (releaseSleepWakeupSemaphore) {
+ BOOL success = ReleaseSemaphore(sleepWakeupSemaphore_, 1, NULL);
+ MOZ_RELEASE_ASSERT(success);
+ }
+
+ // Reacquire the user mutex.
+ EnterCriticalSection(userLock);
+
+ // Return true if woken up, false when timed out.
+ if (waitResult == WAIT_TIMEOUT) {
+ SetLastError(ERROR_TIMEOUT);
+ return false;
+ }
+ return true;
+ }
+
+private:
+ uint32_t sleepersCountAndWakeupMode_;
+ HANDLE sleepWakeupSemaphore_;
+ HANDLE wakeOneEvent_;
+ HANDLE wakeAllEvent_;
+};
+
+struct js::ConditionVariable::PlatformData
+{
+ union
+ {
+ ConditionVariableNative native;
+ ConditionVariableFallback fallback;
+ };
+};
+
+js::ConditionVariable::ConditionVariable()
+{
+ if (sNativeImports.supported())
+ platformData()->native.initialize();
+ else
+ platformData()->fallback.initialize();
+}
+
+void
+js::ConditionVariable::notify_one()
+{
+ if (sNativeImports.supported())
+ platformData()->native.notify_one();
+ else
+ platformData()->fallback.notify_one();
+}
+
+void
+js::ConditionVariable::notify_all()
+{
+ if (sNativeImports.supported())
+ platformData()->native.notify_all();
+ else
+ platformData()->fallback.notify_all();
+}
+
+void
+js::ConditionVariable::wait(UniqueLock<Mutex>& lock)
+{
+ CRITICAL_SECTION* cs = &lock.lock.platformData()->criticalSection;
+ bool r;
+ if (sNativeImports.supported())
+ r = platformData()->native.wait(cs, INFINITE);
+ else
+ r = platformData()->fallback.wait(cs, INFINITE);
+ MOZ_RELEASE_ASSERT(r);
+}
+
+js::CVStatus
+js::ConditionVariable::wait_until(UniqueLock<Mutex>& lock,
+ const mozilla::TimeStamp& abs_time)
+{
+ return wait_for(lock, abs_time - mozilla::TimeStamp::Now());
+}
+
+js::CVStatus
+js::ConditionVariable::wait_for(UniqueLock<Mutex>& lock,
+ const mozilla::TimeDuration& rel_time)
+{
+ CRITICAL_SECTION* cs = &lock.lock.platformData()->criticalSection;
+
+ // Note that DWORD is unsigned, so we have to be careful to clamp at 0.
+ // If rel_time is Forever, then ToMilliseconds is +inf, which evaluates as
+ // greater than UINT32_MAX, resulting in the correct INFINITE wait.
+ double msecd = rel_time.ToMilliseconds();
+ DWORD msec = msecd < 0.0
+ ? 0
+ : msecd > UINT32_MAX
+ ? INFINITE
+ : static_cast<DWORD>(msecd);
+
+ BOOL r;
+ if (sNativeImports.supported())
+ r = platformData()->native.wait(cs, msec);
+ else
+ r = platformData()->fallback.wait(cs, msec);
+ if (r)
+ return CVStatus::NoTimeout;
+ MOZ_RELEASE_ASSERT(GetLastError() == ERROR_TIMEOUT);
+ return CVStatus::Timeout;
+}
+
+js::ConditionVariable::~ConditionVariable()
+{
+ if (sNativeImports.supported())
+ platformData()->native.destroy();
+ else
+ platformData()->fallback.destroy();
+}
+
+inline js::ConditionVariable::PlatformData*
+js::ConditionVariable::platformData()
+{
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/js/src/threading/windows/MutexImpl.cpp b/js/src/threading/windows/MutexImpl.cpp
new file mode 100644
index 000000000..385d1c8de
--- /dev/null
+++ b/js/src/threading/windows/MutexImpl.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "jswin.h"
+
+#include "js/Utility.h"
+
+#include "threading/Mutex.h"
+#include "threading/windows/MutexPlatformData.h"
+
+namespace {
+
+// We build with a toolkit that supports WinXP, so we have to probe
+// for modern features at runtime. This is necessary because Vista and
+// later automatically allocate and subsequently leak a debug info
+// object for each critical section that we allocate unless we tell it
+// not to. In order to tell it not to, we need the extra flags field
+// provided by the Ex version of InitializeCriticalSection.
+struct MutexNativeImports
+{
+ using InitializeCriticalSectionExT = BOOL (WINAPI*)(CRITICAL_SECTION*, DWORD, DWORD);
+ InitializeCriticalSectionExT InitializeCriticalSectionEx;
+
+ MutexNativeImports() {
+ HMODULE kernel32_dll = GetModuleHandle("kernel32.dll");
+ MOZ_RELEASE_ASSERT(kernel32_dll != NULL);
+ InitializeCriticalSectionEx = reinterpret_cast<InitializeCriticalSectionExT>(
+ GetProcAddress(kernel32_dll, "InitializeCriticalSectionEx"));
+ }
+
+ bool hasInitializeCriticalSectionEx() const {
+ return InitializeCriticalSectionEx;
+ }
+};
+
+static MutexNativeImports NativeImports;
+
+} // (anonymous namespace)
+
+js::detail::MutexImpl::MutexImpl()
+{
+ AutoEnterOOMUnsafeRegion oom;
+ platformData_ = js_new<PlatformData>();
+ if (!platformData_)
+ oom.crash("js::Mutex::Mutex");
+
+ // This number was adopted from NSPR.
+ const static DWORD LockSpinCount = 1500;
+ BOOL r;
+ if (NativeImports.hasInitializeCriticalSectionEx()) {
+ r = NativeImports.InitializeCriticalSectionEx(&platformData()->criticalSection,
+ LockSpinCount,
+ CRITICAL_SECTION_NO_DEBUG_INFO);
+ } else {
+ r = InitializeCriticalSectionAndSpinCount(&platformData()->criticalSection,
+ LockSpinCount);
+ }
+ MOZ_RELEASE_ASSERT(r);
+}
+
+js::detail::MutexImpl::~MutexImpl()
+{
+ if (!platformData_)
+ return;
+
+ DeleteCriticalSection(&platformData()->criticalSection);
+ js_delete(platformData());
+}
+
+void
+js::detail::MutexImpl::lock()
+{
+ EnterCriticalSection(&platformData()->criticalSection);
+}
+
+void
+js::detail::MutexImpl::unlock()
+{
+ LeaveCriticalSection(&platformData()->criticalSection);
+}
diff --git a/js/src/threading/windows/MutexPlatformData.h b/js/src/threading/windows/MutexPlatformData.h
new file mode 100644
index 000000000..fbe7fc80d
--- /dev/null
+++ b/js/src/threading/windows/MutexPlatformData.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef platform_win_MutexPlatformData_h
+#define platform_win_MutexPlatformData_h
+
+#include "jswin.h"
+
+#include "threading/Mutex.h"
+
+struct js::detail::MutexImpl::PlatformData
+{
+ CRITICAL_SECTION criticalSection;
+};
+
+#endif // platform_win_MutexPlatformData_h
diff --git a/js/src/threading/windows/Thread.cpp b/js/src/threading/windows/Thread.cpp
new file mode 100644
index 000000000..29e8b16a1
--- /dev/null
+++ b/js/src/threading/windows/Thread.cpp
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include <assert.h>
+#include <new.h>
+#include <process.h>
+
+#include <windows.h>
+
+#include "threading/Thread.h"
+
+class js::Thread::Id::PlatformData
+{
+ friend class js::Thread;
+ friend js::Thread::Id js::ThisThread::GetId();
+
+ HANDLE handle;
+ unsigned id;
+};
+
+/* static */ js::HashNumber
+js::Thread::Hasher::hash(const Lookup& l)
+{
+ return mozilla::HashBytes(l.platformData_, sizeof(l.platformData_));
+}
+
+inline js::Thread::Id::PlatformData*
+js::Thread::Id::platformData()
+{
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
+
+inline const js::Thread::Id::PlatformData*
+js::Thread::Id::platformData() const
+{
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<const PlatformData*>(platformData_);
+}
+
+js::Thread::Id::Id()
+{
+ platformData()->handle = nullptr;
+ platformData()->id = 0;
+}
+
+bool
+js::Thread::Id::operator==(const Id& aOther) const
+{
+ return platformData()->id == aOther.platformData()->id;
+}
+
+js::Thread::Thread(Thread&& aOther)
+{
+ id_ = aOther.id_;
+ aOther.id_ = Id();
+}
+
+js::Thread&
+js::Thread::operator=(Thread&& aOther)
+{
+ MOZ_RELEASE_ASSERT(!joinable());
+ id_ = aOther.id_;
+ aOther.id_ = Id();
+ return *this;
+}
+
+bool
+js::Thread::create(unsigned int (__stdcall* aMain)(void*), void* aArg)
+{
+ // Use _beginthreadex and not CreateThread, because threads that are
+ // created with the latter leak a small amount of memory when they use
+ // certain msvcrt functions and then exit.
+ uintptr_t handle = _beginthreadex(nullptr, options_.stackSize(),
+ aMain, aArg,
+ STACK_SIZE_PARAM_IS_A_RESERVATION,
+ &id_.platformData()->id);
+ if (!handle) {
+ // The documentation does not say what state the thread id has if the method
+ // fails, so assume that it is undefined and reset it manually.
+ id_ = Id();
+ return false;
+ }
+ id_.platformData()->handle = reinterpret_cast<HANDLE>(handle);
+ return true;
+}
+
+void
+js::Thread::join()
+{
+ MOZ_RELEASE_ASSERT(joinable());
+ DWORD r = WaitForSingleObject(id_.platformData()->handle, INFINITE);
+ MOZ_RELEASE_ASSERT(r == WAIT_OBJECT_0);
+ BOOL success = CloseHandle(id_.platformData()->handle);
+ MOZ_RELEASE_ASSERT(success);
+ id_ = Id();
+}
+
+void
+js::Thread::detach()
+{
+ MOZ_RELEASE_ASSERT(joinable());
+ BOOL success = CloseHandle(id_.platformData()->handle);
+ MOZ_RELEASE_ASSERT(success);
+ id_ = Id();
+}
+
+js::Thread::Id
+js::ThisThread::GetId()
+{
+ js::Thread::Id id;
+ id.platformData()->handle = GetCurrentThread();
+ id.platformData()->id = GetCurrentThreadId();
+ MOZ_RELEASE_ASSERT(id != js::Thread::Id());
+ return id;
+}
+
+void
+js::ThisThread::SetName(const char* name)
+{
+ MOZ_RELEASE_ASSERT(name);
+
+#ifdef _MSC_VER
+ // Setting the thread name requires compiler support for structured
+ // exceptions, so this only works when compiled with MSVC.
+ static const DWORD THREAD_NAME_EXCEPTION = 0x406D1388;
+ static const DWORD THREAD_NAME_INFO_TYPE = 0x1000;
+
+#pragma pack(push, 8)
+ struct THREADNAME_INFO
+ {
+ DWORD dwType;
+ LPCSTR szName;
+ DWORD dwThreadID;
+ DWORD dwFlags;
+ };
+#pragma pack(pop)
+
+ THREADNAME_INFO info;
+ info.dwType = THREAD_NAME_INFO_TYPE;
+ info.szName = name;
+ info.dwThreadID = GetCurrentThreadId();
+ info.dwFlags = 0;
+
+ __try {
+ RaiseException(THREAD_NAME_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR),
+ (ULONG_PTR*)&info);
+ } __except (EXCEPTION_EXECUTE_HANDLER) {
+ // Do nothing.
+ }
+#endif
+}
+
+void
+js::ThisThread::GetName(char* nameBuffer, size_t len)
+{
+ MOZ_RELEASE_ASSERT(len > 0);
+ *nameBuffer = '\0';
+}