diff options
Diffstat (limited to 'js/src/threading')
-rw-r--r-- | js/src/threading/ConditionVariable.h | 121 | ||||
-rw-r--r-- | js/src/threading/ExclusiveData.h | 197 | ||||
-rw-r--r-- | js/src/threading/LockGuard.h | 52 | ||||
-rw-r--r-- | js/src/threading/Mutex.cpp | 74 | ||||
-rw-r--r-- | js/src/threading/Mutex.h | 128 | ||||
-rw-r--r-- | js/src/threading/Thread.h | 243 | ||||
-rw-r--r-- | js/src/threading/posix/ConditionVariable.cpp | 180 | ||||
-rw-r--r-- | js/src/threading/posix/MutexImpl.cpp | 81 | ||||
-rw-r--r-- | js/src/threading/posix/MutexPlatformData.h | 19 | ||||
-rw-r--r-- | js/src/threading/posix/Thread.cpp | 182 | ||||
-rw-r--r-- | js/src/threading/windows/ConditionVariable.cpp | 418 | ||||
-rw-r--r-- | js/src/threading/windows/MutexImpl.cpp | 85 | ||||
-rw-r--r-- | js/src/threading/windows/MutexPlatformData.h | 19 | ||||
-rw-r--r-- | js/src/threading/windows/Thread.cpp | 164 |
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'; +} |