diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/threading | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/threading')
-rw-r--r-- | js/src/threading/ConditionVariable.h | 138 | ||||
-rw-r--r-- | js/src/threading/CpuCount.h | 16 | ||||
-rw-r--r-- | js/src/threading/ExclusiveData.h | 425 | ||||
-rw-r--r-- | js/src/threading/LockGuard.h | 43 | ||||
-rw-r--r-- | js/src/threading/Mutex.cpp | 85 | ||||
-rw-r--r-- | js/src/threading/Mutex.h | 94 | ||||
-rw-r--r-- | js/src/threading/ProtectedData.cpp | 106 | ||||
-rw-r--r-- | js/src/threading/ProtectedData.h | 371 | ||||
-rw-r--r-- | js/src/threading/Thread.cpp | 32 | ||||
-rw-r--r-- | js/src/threading/Thread.h | 228 | ||||
-rw-r--r-- | js/src/threading/ThreadId.h | 38 | ||||
-rw-r--r-- | js/src/threading/noop/CpuCount.cpp | 9 | ||||
-rw-r--r-- | js/src/threading/noop/NoopThread.cpp | 34 | ||||
-rw-r--r-- | js/src/threading/noop/ThreadPlatformData.h | 24 | ||||
-rw-r--r-- | js/src/threading/posix/CpuCount.cpp | 28 | ||||
-rw-r--r-- | js/src/threading/posix/PosixThread.cpp | 146 | ||||
-rw-r--r-- | js/src/threading/posix/ThreadPlatformData.h | 42 | ||||
-rw-r--r-- | js/src/threading/windows/CpuCount.cpp | 20 | ||||
-rw-r--r-- | js/src/threading/windows/ThreadPlatformData.h | 30 | ||||
-rw-r--r-- | js/src/threading/windows/WindowsThread.cpp | 127 |
20 files changed, 2036 insertions, 0 deletions
diff --git a/js/src/threading/ConditionVariable.h b/js/src/threading/ConditionVariable.h new file mode 100644 index 0000000000..e2b8865cbf --- /dev/null +++ b/js/src/threading/ConditionVariable.h @@ -0,0 +1,138 @@ +/* -*- 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/PlatformConditionVariable.h" +#include "mozilla/TimeStamp.h" + +#include <stdint.h> +#include <utility> +#if !defined(XP_WIN) && !defined(__wasi__) +# include <pthread.h> +#endif + +#include "threading/LockGuard.h" +#include "threading/Mutex.h" + +namespace js { + +template <class T> +class ExclusiveData; + +enum class CVStatus { NoTimeout, Timeout }; + +template <typename T> +using UniqueLock = LockGuard<T>; + +// A poly-fill for std::condition_variable. +class ConditionVariable { + public: + struct PlatformData; + + ConditionVariable() = default; + ~ConditionVariable() = default; + + // Wake one thread that is waiting on this condition. + void notify_one() { impl_.notify_one(); } + + // Wake all threads that are waiting on this condition. + void notify_all() { impl_.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(Mutex& lock) { +#ifdef DEBUG + lock.preUnlockChecks(); +#endif + impl_.wait(lock.impl_); +#ifdef DEBUG + lock.preLockChecks(); + lock.postLockChecks(); +#endif + } + void wait(UniqueLock<Mutex>& lock) { wait(lock.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) { + return wait_for(lock, abs_time - mozilla::TimeStamp::Now()); + } + + // 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) { +#ifdef DEBUG + lock.lock.preUnlockChecks(); +#endif + CVStatus res = + impl_.wait_for(lock.lock.impl_, rel_time) == mozilla::CVStatus::Timeout + ? CVStatus::Timeout + : CVStatus::NoTimeout; +#ifdef DEBUG + lock.lock.preLockChecks(); + lock.lock.postLockChecks(); +#endif + return res; + } + + // 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, + std::move(pred)); + } + + private: + ConditionVariable(const ConditionVariable&) = delete; + ConditionVariable& operator=(const ConditionVariable&) = delete; + template <class T> + friend class ExclusiveWaitableData; + + mozilla::detail::ConditionVariableImpl impl_; +}; + +} // namespace js + +#endif // threading_ConditionVariable_h diff --git a/js/src/threading/CpuCount.h b/js/src/threading/CpuCount.h new file mode 100644 index 0000000000..dac6c46ea8 --- /dev/null +++ b/js/src/threading/CpuCount.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <inttypes.h> + +namespace js { + +/* + * Return the number of cores on the system. If the system provides logical + * cores (such as hyperthreads) then count each logical core as an actual core. + */ +uint32_t GetCPUCount(); + +} // namespace js diff --git a/js/src/threading/ExclusiveData.h b/js/src/threading/ExclusiveData.h new file mode 100644 index 0000000000..38e89f10a1 --- /dev/null +++ b/js/src/threading/ExclusiveData.h @@ -0,0 +1,425 @@ +/* -*- 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_ExclusiveData_h +#define threading_ExclusiveData_h + +#include "mozilla/Maybe.h" +#include "mozilla/OperatorNewExtensions.h" + +#include <utility> + +#include "threading/ConditionVariable.h" +#include "threading/Mutex.h" + +namespace js { + +/** + * [SMDOC] ExclusiveData API + * + * 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 { + protected: + mutable Mutex lock_ MOZ_UNANNOTATED; + mutable 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), value_(std::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), value_(std::forward<Args>(args)...) {} + + ExclusiveData(ExclusiveData&& rhs) + : lock_(std::move(rhs.lock)), value_(std::move(rhs.value_)) { + MOZ_ASSERT(&rhs != this, "self-move disallowed!"); + } + + ExclusiveData& operator=(ExclusiveData&& rhs) { + this->~ExclusiveData(); + new (mozilla::KnownNotNull, this) ExclusiveData(std::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 { + protected: + const ExclusiveData* parent_; + explicit Guard(std::nullptr_t) : parent_(nullptr) {} + + private: + 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(std::move(rhs)); + return *this; + } + + T& get() const { + MOZ_ASSERT(parent_); + return parent_->value_; + } + + 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(); + } + } + }; + + /** + * NullableGuard are similar to Guard, except that one the access to the + * ExclusiveData might not always be granted. This is useful when contextual + * information is enough to prevent useless use of Mutex. + * + * The NullableGuard can be manipulated as follows: + * + * if (NullableGuard guard = data.mightAccess()) { + * // NullableGuard is acquired. + * guard->... + * } + * // NullableGuard was either not acquired or released. + * + * Where mightAccess returns either a NullableGuard from `noAccess()` or a + * Guard from `lock()`. + */ + class MOZ_STACK_CLASS NullableGuard : public Guard { + public: + explicit NullableGuard(std::nullptr_t) : Guard((std::nullptr_t) nullptr) {} + explicit NullableGuard(const ExclusiveData& parent) : Guard(parent) {} + explicit NullableGuard(Guard&& rhs) : Guard(std::move(rhs)) {} + + NullableGuard& operator=(Guard&& rhs) { + this->~NullableGuard(); + new (this) NullableGuard(std::move(rhs)); + return *this; + } + + /** + * Returns whether this NullableGuard has access to the exclusive data. + */ + bool hasAccess() const { return this->parent_; } + explicit operator bool() const { return hasAccess(); } + }; + + /** + * Access the protected inner `T` value for exclusive reading and writing. + */ + Guard lock() const { return Guard(*this); } + + /** + * Provide a no-access guard, which coerces to false when tested. This value + * can be returned if the guard access is conditioned on external factors. + * + * See NullableGuard. + */ + NullableGuard noAccess() const { + return NullableGuard((std::nullptr_t) nullptr); + } +}; + +template <class T> +class ExclusiveWaitableData : public ExclusiveData<T> { + using Base = ExclusiveData<T>; + + mutable ConditionVariable condVar_; + + public: + template <typename U> + explicit ExclusiveWaitableData(const MutexId& id, U&& u) + : Base(id, std::forward<U>(u)) {} + + template <typename... Args> + explicit ExclusiveWaitableData(const MutexId& id, Args&&... args) + : Base(id, std::forward<Args>(args)...) {} + + class MOZ_STACK_CLASS Guard : public ExclusiveData<T>::Guard { + using Base = typename ExclusiveData<T>::Guard; + + public: + explicit Guard(const ExclusiveWaitableData& parent) : Base(parent) {} + + Guard(Guard&& guard) : Base(std::move(guard)) {} + + Guard& operator=(Guard&& rhs) { return Base::operator=(std::move(rhs)); } + + void wait() { + auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); + parent->condVar_.wait(parent->lock_); + } + + void notify_one() { + auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); + parent->condVar_.notify_one(); + } + + void notify_all() { + auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); + parent->condVar_.notify_all(); + } + }; + + Guard lock() const { return Guard(*this); } +}; + +/** + * Multiple-readers / single-writer variant of ExclusiveData. + * + * Readers call readLock() to obtain a stack-only RAII reader lock, which will + * allow other readers to read concurrently but block writers; the yielded value + * is const. Writers call writeLock() to obtain a ditto writer lock, which + * yields exclusive access to non-const data. + * + * See ExclusiveData and its implementation for more documentation. + */ +template <typename T> +class RWExclusiveData { + mutable Mutex lock_ MOZ_UNANNOTATED; + mutable ConditionVariable cond_; + mutable T value_; + mutable int readers_; + + // We maintain a count of active readers. Writers may enter the critical + // section only when the reader count is zero, so the reader that decrements + // the count to zero must wake up any waiting writers. + // + // There can be multiple writers waiting, so a writer leaving the critical + // section must also wake up any other waiting writers. + + void acquireReaderLock() const { + lock_.lock(); + readers_++; + lock_.unlock(); + } + + void releaseReaderLock() const { + lock_.lock(); + MOZ_ASSERT(readers_ > 0); + if (--readers_ == 0) { + cond_.notify_all(); + } + lock_.unlock(); + } + + void acquireWriterLock() const { + lock_.lock(); + while (readers_ > 0) { + cond_.wait(lock_); + } + } + + void releaseWriterLock() const { + cond_.notify_all(); + lock_.unlock(); + } + + public: + RWExclusiveData(const RWExclusiveData&) = delete; + RWExclusiveData& operator=(const RWExclusiveData&) = delete; + + /** + * Create a new `RWExclusiveData`, constructing the protected value in place. + */ + template <typename... Args> + explicit RWExclusiveData(const MutexId& id, Args&&... args) + : lock_(id), value_(std::forward<Args>(args)...), readers_(0) {} + + class MOZ_STACK_CLASS ReadGuard { + const RWExclusiveData* parent_; + explicit ReadGuard(std::nullptr_t) : parent_(nullptr) {} + + public: + ReadGuard(const ReadGuard&) = delete; + ReadGuard& operator=(const ReadGuard&) = delete; + + explicit ReadGuard(const RWExclusiveData& parent) : parent_(&parent) { + parent_->acquireReaderLock(); + } + + ReadGuard(ReadGuard&& rhs) : parent_(rhs.parent_) { + MOZ_ASSERT(&rhs != this, "self-move disallowed!"); + rhs.parent_ = nullptr; + } + + ReadGuard& operator=(ReadGuard&& rhs) { + this->~ReadGuard(); + new (this) ReadGuard(std::move(rhs)); + return *this; + } + + const T& get() const { + MOZ_ASSERT(parent_); + return parent_->value_; + } + + operator const T&() const { return get(); } + const T* operator->() const { return &get(); } + + const RWExclusiveData<T>* parent() const { + MOZ_ASSERT(parent_); + return parent_; + } + + ~ReadGuard() { + if (parent_) { + parent_->releaseReaderLock(); + } + } + }; + + class MOZ_STACK_CLASS WriteGuard { + const RWExclusiveData* parent_; + explicit WriteGuard(std::nullptr_t) : parent_(nullptr) {} + + public: + WriteGuard(const WriteGuard&) = delete; + WriteGuard& operator=(const WriteGuard&) = delete; + + explicit WriteGuard(const RWExclusiveData& parent) : parent_(&parent) { + parent_->acquireWriterLock(); + } + + WriteGuard(WriteGuard&& rhs) : parent_(rhs.parent_) { + MOZ_ASSERT(&rhs != this, "self-move disallowed!"); + rhs.parent_ = nullptr; + } + + WriteGuard& operator=(WriteGuard&& rhs) { + this->~WriteGuard(); + new (this) WriteGuard(std::move(rhs)); + return *this; + } + + T& get() const { + MOZ_ASSERT(parent_); + return parent_->value_; + } + + operator T&() const { return get(); } + T* operator->() const { return &get(); } + + const RWExclusiveData<T>* parent() const { + MOZ_ASSERT(parent_); + return parent_; + } + + ~WriteGuard() { + if (parent_) { + parent_->releaseWriterLock(); + } + } + }; + + ReadGuard readLock() const { return ReadGuard(*this); } + WriteGuard writeLock() const { return WriteGuard(*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 0000000000..80647012b9 --- /dev/null +++ b/js/src/threading/LockGuard.h @@ -0,0 +1,43 @@ +/* -*- 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 + +#include "mozilla/Attributes.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 0000000000..8a57db4755 --- /dev/null +++ b/js/src/threading/Mutex.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 "threading/Mutex.h" + +using namespace js; + +#ifdef DEBUG + +MOZ_THREAD_LOCAL(js::Mutex*) js::Mutex::HeldMutexStack; + +/* static */ +bool js::Mutex::Init() { return HeldMutexStack.init(); } + +void js::Mutex::lock() { + preLockChecks(); + impl_.lock(); + postLockChecks(); +} + +bool js::Mutex::tryLock() { + preLockChecks(); + if (!impl_.tryLock()) { + return false; + } + + postLockChecks(); + return true; +} + +void js::Mutex::preLockChecks() const { + Mutex* prev = HeldMutexStack.get(); + if (prev) { + if (id_.order <= prev->id_.order) { + fprintf(stderr, + "Attempt to acquire mutex %s with order %u while holding %s with " + "order %u\n", + id_.name, id_.order, prev->id_.name, prev->id_.order); + MOZ_CRASH("Mutex ordering violation"); + } + } +} + +void js::Mutex::postLockChecks() { + MOZ_ASSERT(!owningThread_); + owningThread_ = ThreadId::ThisThreadId(); + + MOZ_ASSERT(prev_ == nullptr); + prev_ = HeldMutexStack.get(); + HeldMutexStack.set(this); +} + +void js::Mutex::unlock() { + preUnlockChecks(); + impl_.unlock(); +} + +void js::Mutex::preUnlockChecks() { + Mutex* stack = HeldMutexStack.get(); + MOZ_ASSERT(stack == this); + HeldMutexStack.set(prev_); + prev_ = nullptr; + + MOZ_ASSERT(ThreadId::ThisThreadId() == owningThread_); + owningThread_ = ThreadId(); +} + +void js::Mutex::assertOwnedByCurrentThread() const { + // This check is only thread-safe if it succeeds. + MOZ_ASSERT(ThreadId::ThisThreadId() == owningThread_); + + // Check the mutex is on the mutex stack. + for (Mutex* mutex = HeldMutexStack.get(); mutex; mutex = mutex->prev_) { + if (mutex == this) { + return; + } + } + + MOZ_CRASH("Mutex not found on the stack of held mutexes"); +} + +#endif diff --git a/js/src/threading/Mutex.h b/js/src/threading/Mutex.h new file mode 100644 index 0000000000..5d21b3db30 --- /dev/null +++ b/js/src/threading/Mutex.h @@ -0,0 +1,94 @@ +/* -*- 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/Maybe.h" +#include "mozilla/PlatformMutex.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/Vector.h" + +#include <utility> + +#include "threading/ThreadId.h" + +namespace js { + +// 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; +}; + +// The Mutex class below wraps mozilla::detail::MutexImpl, but we don't want to +// use public inheritance, and private inheritance is problematic because +// Mutex's friends can access the private parent class as if it was public +// inheritance. So use a data member, but for Mutex to access the data member +// we must override it and make Mutex a friend. +class MutexImpl : public mozilla::detail::MutexImpl { + protected: + MutexImpl() : mozilla::detail::MutexImpl() {} + + friend class Mutex; +}; + +// 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 { + private: + MutexImpl impl_; + +#ifdef DEBUG + const MutexId id_; + Mutex* prev_ = nullptr; + ThreadId owningThread_; + + static MOZ_THREAD_LOCAL(Mutex*) HeldMutexStack; +#endif + + public: +#ifdef DEBUG + static bool Init(); + + explicit Mutex(const MutexId& id) : id_(id) { MOZ_ASSERT(id_.order != 0); } + + void lock(); + bool tryLock(); + void unlock(); + void assertOwnedByCurrentThread() const; +#else + static bool Init() { return true; } + + explicit Mutex(const MutexId& id) {} + + void lock() { impl_.lock(); } + bool tryLock() { return impl_.tryLock(); } + void unlock() { impl_.unlock(); } + void assertOwnedByCurrentThread() const {}; +#endif + + private: +#ifdef DEBUG + void preLockChecks() const; + void postLockChecks(); + void preUnlockChecks(); +#endif + + friend class ConditionVariable; +}; + +} // namespace js + +#endif // threading_Mutex_h diff --git a/js/src/threading/ProtectedData.cpp b/js/src/threading/ProtectedData.cpp new file mode 100644 index 0000000000..9843fd5784 --- /dev/null +++ b/js/src/threading/ProtectedData.cpp @@ -0,0 +1,106 @@ +/* -*- 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/ProtectedData.h" + +#include "vm/HelperThreads.h" +#include "vm/JSContext.h" + +namespace js { + +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + +/* static */ mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> + AutoNoteSingleThreadedRegion::count(0); + +template <AllowedHelperThread Helper> +static inline bool OnHelperThread() { + if (Helper == AllowedHelperThread::IonCompile || + Helper == AllowedHelperThread::GCTaskOrIonCompile || + Helper == AllowedHelperThread::ParseTaskOrIonCompile) { + if (CurrentThreadIsIonCompiling()) { + return true; + } + } + + if (Helper == AllowedHelperThread::GCTask || + Helper == AllowedHelperThread::GCTaskOrIonCompile) { + if (CurrentThreadIsPerformingGC()) { + return true; + } + } + + if (Helper == AllowedHelperThread::ParseTask || + Helper == AllowedHelperThread::ParseTaskOrIonCompile) { + if (CurrentThreadIsParseThread()) { + return true; + } + } + + return false; +} + +void CheckThreadLocal::check() const { + JSContext* cx = TlsContext.get(); + MOZ_ASSERT(cx); + MOZ_ASSERT_IF(cx->isMainThreadContext(), + CurrentThreadCanAccessRuntime(cx->runtime())); + MOZ_ASSERT(id == ThreadId::ThisThreadId()); +} + +void CheckContextLocal::check() const { + if (!cx_->isInitialized()) { + return; + } + + JSContext* cx = TlsContext.get(); + MOZ_ASSERT(cx); + MOZ_ASSERT_IF(cx->isMainThreadContext(), + CurrentThreadCanAccessRuntime(cx->runtime())); + MOZ_ASSERT(cx_ == cx); +} + +template <AllowedHelperThread Helper> +void CheckMainThread<Helper>::check() const { + if (OnHelperThread<Helper>()) { + return; + } + + JSContext* cx = TlsContext.get(); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); +} + +template class CheckMainThread<AllowedHelperThread::None>; +template class CheckMainThread<AllowedHelperThread::GCTask>; +template class CheckMainThread<AllowedHelperThread::ParseTask>; +template class CheckMainThread<AllowedHelperThread::IonCompile>; +template class CheckMainThread<AllowedHelperThread::ParseTaskOrIonCompile>; + +template <GlobalLock Lock, AllowedHelperThread Helper> +void CheckGlobalLock<Lock, Helper>::check() const { + if (OnHelperThread<Helper>()) { + return; + } + + switch (Lock) { + case GlobalLock::GCLock: + TlsGCContext.get() + ->runtimeFromAnyThread() + ->gc.assertCurrentThreadHasLockedGC(); + break; + case GlobalLock::HelperThreadLock: + gHelperThreadLock.assertOwnedByCurrentThread(); + break; + } +} + +template class CheckGlobalLock<GlobalLock::GCLock, AllowedHelperThread::None>; +template class CheckGlobalLock<GlobalLock::HelperThreadLock, + AllowedHelperThread::None>; + +#endif // JS_HAS_PROTECTED_DATA_CHECKS + +} // namespace js diff --git a/js/src/threading/ProtectedData.h b/js/src/threading/ProtectedData.h new file mode 100644 index 0000000000..adcd4d31f4 --- /dev/null +++ b/js/src/threading/ProtectedData.h @@ -0,0 +1,371 @@ +/* -*- 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_ProtectedData_h +#define threading_ProtectedData_h + +#include "mozilla/Atomics.h" +#include "jstypes.h" +#include "threading/LockGuard.h" +#include "threading/Mutex.h" +#include "threading/ThreadId.h" + +struct JS_PUBLIC_API JSContext; + +namespace js { + +// This file provides classes for encapsulating pieces of data with a check +// that ensures the data is only accessed if certain conditions are met. +// Checking is only done in debug builds; in release builds these classes +// have no space or time overhead. These classes are mainly used for ensuring +// that data is used in threadsafe ways. +// +// ProtectedData does not by itself ensure that data is threadsafe: it only +// documents and checks synchronization constraints that need to be established +// by the code using the data. If a mutex can be created and directly +// associated with the data, consider using the ExclusiveData class instead. +// Otherwise, ProtectedData should be used to document whatever synchronization +// method is used. + +// Protected data checks are enabled in debug builds, except on android where +// they cause some permatimeouts in automation. +#if defined(DEBUG) && !defined(ANDROID) +# define JS_HAS_PROTECTED_DATA_CHECKS +#endif + +#define DECLARE_ONE_BOOL_OPERATOR(OP, T) \ + template <typename U> \ + bool operator OP(const U& other) const { \ + if constexpr (std::is_integral_v<T>) { \ + return ref() OP static_cast<T>(other); \ + } else { \ + return ref() OP other; \ + } \ + } + +#define DECLARE_BOOL_OPERATORS(T) \ + DECLARE_ONE_BOOL_OPERATOR(==, T) \ + DECLARE_ONE_BOOL_OPERATOR(!=, T) \ + DECLARE_ONE_BOOL_OPERATOR(<=, T) \ + DECLARE_ONE_BOOL_OPERATOR(>=, T) \ + DECLARE_ONE_BOOL_OPERATOR(<, T) \ + DECLARE_ONE_BOOL_OPERATOR(>, T) + +// Mark a region of code that should be treated as single threaded and suppress +// any ProtectedData checks. +// +// Note that in practice there may be multiple threads running when this class +// is used, due to the presence of multiple runtimes in the process. When each +// process has only a single runtime this will no longer be a concern. +class MOZ_RAII AutoNoteSingleThreadedRegion { + public: +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + static mozilla::Atomic<size_t, mozilla::SequentiallyConsistent> count; + AutoNoteSingleThreadedRegion() { count++; } + ~AutoNoteSingleThreadedRegion() { count--; } +#else + AutoNoteSingleThreadedRegion() {} +#endif +}; + +// Class for protected data that may be written to any number of times. Checks +// occur when the data is both read from and written to. +template <typename Check, typename T> +class ProtectedData { + typedef ProtectedData<Check, T> ThisType; + + public: + template <typename... Args> + explicit ProtectedData(const Check& check, Args&&... args) + : value(std::forward<Args>(args)...) +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + , + check(check) +#endif + { + } + + DECLARE_BOOL_OPERATORS(T) + + operator const T&() const { return ref(); } + const T& operator->() const { return ref(); } + + template <typename U> + ThisType& operator=(const U& p) { + this->ref() = p; + return *this; + } + + template <typename U> + ThisType& operator=(U&& p) { + this->ref() = std::move(p); + return *this; + } + + template <typename U> + T& operator+=(const U& rhs) { + return ref() += rhs; + } + template <typename U> + T& operator-=(const U& rhs) { + return ref() -= rhs; + } + template <typename U> + T& operator*=(const U& rhs) { + return ref() *= rhs; + } + template <typename U> + T& operator/=(const U& rhs) { + return ref() /= rhs; + } + template <typename U> + T& operator&=(const U& rhs) { + return ref() &= rhs; + } + template <typename U> + T& operator|=(const U& rhs) { + return ref() |= rhs; + } + T& operator++() { return ++ref(); } + T& operator--() { return --ref(); } + T operator++(int) { return ref()++; } + T operator--(int) { return ref()--; } + + T& ref() { +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + if (!AutoNoteSingleThreadedRegion::count) { + check.check(); + } +#endif + return value; + } + + const T& ref() const { +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + if (!AutoNoteSingleThreadedRegion::count) { + check.check(); + } +#endif + return value; + } + + T& refNoCheck() { return value; } + const T& refNoCheck() const { return value; } + + static size_t offsetOfValue() { return offsetof(ThisType, value); } + + private: + T value; +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + Check check; +#endif +}; + +// Intermediate class for protected data whose checks take no constructor +// arguments. +template <typename Check, typename T> +class ProtectedDataNoCheckArgs : public ProtectedData<Check, T> { + using Base = ProtectedData<Check, T>; + + public: + template <typename... Args> + explicit ProtectedDataNoCheckArgs(Args&&... args) + : ProtectedData<Check, T>(Check(), std::forward<Args>(args)...) {} + + using Base::operator=; +}; + +// Intermediate class for protected data whose checks take a JSContext. +template <typename Check, typename T> +class ProtectedDataContextArg : public ProtectedData<Check, T> { + using Base = ProtectedData<Check, T>; + + public: + template <typename... Args> + explicit ProtectedDataContextArg(JSContext* cx, Args&&... args) + : ProtectedData<Check, T>(Check(cx), std::forward<Args>(args)...) {} + + using Base::operator=; +}; + +class CheckUnprotected { +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + public: + inline void check() const {} +#endif +}; + +// Data with a no-op check that permits all accesses. This is tantamount to not +// using ProtectedData at all, but is in place to document points which need +// to be fixed in order for runtimes to be multithreaded (see bug 1323066). +template <typename T> +using UnprotectedData = ProtectedDataNoCheckArgs<CheckUnprotected, T>; + +class CheckThreadLocal { +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + ThreadId id; + + public: + CheckThreadLocal() : id(ThreadId::ThisThreadId()) {} + + void check() const; +#endif +}; + +class CheckContextLocal { +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + JSContext* cx_; + + public: + explicit CheckContextLocal(JSContext* cx) : cx_(cx) {} + + void check() const; +#else + public: + explicit CheckContextLocal(JSContext* cx) {} +#endif +}; + +// Data which may only be accessed by the thread on which it is created. +template <typename T> +using ThreadData = ProtectedDataNoCheckArgs<CheckThreadLocal, T>; + +// Data which belongs to a JSContext and should only be accessed from that +// JSContext's thread. Note that a JSContext may not have a thread currently +// associated with it and any associated thread may change over time. +template <typename T> +using ContextData = ProtectedDataContextArg<CheckContextLocal, T>; + +// Enum describing which helper threads (GC tasks or Ion compilations) may +// access data. +enum class AllowedHelperThread { + None, + GCTask, + ParseTask, + IonCompile, + GCTaskOrIonCompile, + ParseTaskOrIonCompile, +}; + +template <AllowedHelperThread Helper> +class CheckMainThread { + public: + void check() const; +}; + +// Data which may only be accessed by the runtime's main thread. +template <typename T> +using MainThreadData = + ProtectedDataNoCheckArgs<CheckMainThread<AllowedHelperThread::None>, T>; + +// Data which may only be accessed by the runtime's main thread or by various +// helper thread tasks. +template <typename T> +using MainThreadOrGCTaskData = + ProtectedDataNoCheckArgs<CheckMainThread<AllowedHelperThread::GCTask>, T>; +template <typename T> +using MainThreadOrIonCompileData = + ProtectedDataNoCheckArgs<CheckMainThread<AllowedHelperThread::IonCompile>, + T>; +template <typename T> +using MainThreadOrParseData = + ProtectedDataNoCheckArgs<CheckMainThread<AllowedHelperThread::ParseTask>, + T>; + +template <typename T> +using MainThreadOrParseOrIonCompileData = ProtectedDataNoCheckArgs< + CheckMainThread<AllowedHelperThread::ParseTaskOrIonCompile>, T>; + +// Runtime wide locks which might protect some data. +enum class GlobalLock { GCLock, HelperThreadLock }; + +template <GlobalLock Lock, AllowedHelperThread Helper> +class CheckGlobalLock { +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + public: + void check() const; +#endif +}; + +// Data which may only be accessed while holding the GC lock. +template <typename T> +using GCLockData = ProtectedDataNoCheckArgs< + CheckGlobalLock<GlobalLock::GCLock, AllowedHelperThread::None>, T>; + +// Data which may only be accessed while holding the helper thread lock. +template <typename T> +using HelperThreadLockData = ProtectedDataNoCheckArgs< + CheckGlobalLock<GlobalLock::HelperThreadLock, AllowedHelperThread::None>, + T>; + +// Class for protected data that is only written to once. 'const' may sometimes +// be usable instead of this class, but in cases where the data cannot be set +// to its final value in its constructor this class is helpful. Protected data +// checking only occurs when writes are performed, not reads. Steps may need to +// be taken to ensure that reads do not occur until the written value is fully +// initialized, as such guarantees are not provided by this class. +template <typename Check, typename T> +class ProtectedDataWriteOnce { + typedef ProtectedDataWriteOnce<Check, T> ThisType; + + public: + template <typename... Args> + explicit ProtectedDataWriteOnce(Args&&... args) + : value(std::forward<Args>(args)...) +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + , + nwrites(0) +#endif + { + } + + DECLARE_BOOL_OPERATORS(T) + + operator const T&() const { return ref(); } + const T& operator->() const { return ref(); } + + template <typename U> + ThisType& operator=(const U& p) { + if (ref() != p) { + this->writeRef() = p; + } + return *this; + } + + const T& ref() const { return value; } + + T& writeRef() { +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + if (!AutoNoteSingleThreadedRegion::count) { + check.check(); + } + // Despite the WriteOnce name, actually allow two writes to accommodate + // data that is cleared during teardown. + MOZ_ASSERT(++nwrites <= 2); +#endif + return value; + } + + private: + T value; +#ifdef JS_HAS_PROTECTED_DATA_CHECKS + Check check; + size_t nwrites; +#endif +}; + +// Data that is written once with no requirements for exclusive access when +// that write occurs. +template <typename T> +using WriteOnceData = ProtectedDataWriteOnce<CheckUnprotected, T>; + +#undef DECLARE_ASSIGNMENT_OPERATOR +#undef DECLARE_ONE_BOOL_OPERATOR +#undef DECLARE_BOOL_OPERATORS + +} // namespace js + +#endif // threading_ProtectedData_h diff --git a/js/src/threading/Thread.cpp b/js/src/threading/Thread.cpp new file mode 100644 index 0000000000..26b1cfe429 --- /dev/null +++ b/js/src/threading/Thread.cpp @@ -0,0 +1,32 @@ +/* -*- 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/Thread.h" +#include "mozilla/Assertions.h" + +namespace js { + +Thread::~Thread() { MOZ_RELEASE_ASSERT(!joinable()); } + +Thread::Thread(Thread&& aOther) { + id_ = aOther.id_; + aOther.id_ = ThreadId(); + options_ = aOther.options_; +} + +Thread& Thread::operator=(Thread&& aOther) { + MOZ_RELEASE_ASSERT(!joinable()); + id_ = aOther.id_; + aOther.id_ = ThreadId(); + options_ = aOther.options_; + return *this; +} + +ThreadId Thread::get_id() { return id_; } + +bool Thread::joinable() { return id_ != ThreadId(); } + +} // namespace js diff --git a/js/src/threading/Thread.h b/js/src/threading/Thread.h new file mode 100644 index 0000000000..89bb23b42e --- /dev/null +++ b/js/src/threading/Thread.h @@ -0,0 +1,228 @@ +/* -*- 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/TimeStamp.h" + +#include <stdint.h> +#include <type_traits> +#include <utility> + +#include "js/Initialization.h" +#include "js/Utility.h" +#include "threading/LockGuard.h" +#include "threading/Mutex.h" +#include "threading/ThreadId.h" +#include "vm/MutexIDs.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: + // 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_; } + }; + + // 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 = std::remove_const_t<O>, + typename DerefO = std::remove_reference_t<NonConstO>, + typename = std::enable_if_t<std::is_same_v<DerefO, Options>>> + explicit Thread(O&& options = Options()) + : id_(ThreadId()), options_(std::forward<O>(options)) { + MOZ_ASSERT(isInitialized()); + } + + // 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 (std::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> + [[nodiscard]] bool init(F&& f, Args&&... args) { + MOZ_RELEASE_ASSERT(id_ == ThreadId()); + using Trampoline = detail::ThreadTrampoline<F, Args...>; + auto trampoline = + js_new<Trampoline>(std::forward<F>(f), std::forward<Args>(args)...); + if (!trampoline) { + return false; + } + + // We hold this lock while create() sets the thread id. + LockGuard<Mutex> lock(trampoline->createMutex); + return create(Trampoline::Start, trampoline); + } + + // The thread must be joined or detached before destruction. + ~Thread(); + + // 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(); + + // 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. + ThreadId get_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. + ThreadId id_; + + // Overridable thread creation options. + Options options_; + + // Dispatch to per-platform implementation of thread creation. + [[nodiscard]] bool create(THREAD_RETURN_TYPE(THREAD_CALL_API* aMain)(void*), + void* aArg); + + // An internal version of JS_IsInitialized() that returns whether SpiderMonkey + // is currently initialized or is in the process of being initialized. + static inline bool isInitialized() { + using namespace JS::detail; + return libraryInitState == InitState::Initializing || + libraryInitState == InitState::Running; + } +}; + +namespace ThisThread { + +// 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); + +// Causes the current thread to sleep until the +// number of real-time milliseconds specified have elapsed. +void SleepMilliseconds(size_t ms); + +} // 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. + std::tuple<std::decay_t<Args>...> args; + + // Protect the thread id during creation. + Mutex createMutex MOZ_UNANNOTATED; + + // Thread can access createMutex. + friend class js::Thread; + + 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(std::forward<F>(aG)), + args(std::forward<Args>(aArgsT)...), + createMutex(mutexid::ThreadId) {} + + static THREAD_RETURN_TYPE THREAD_CALL_API Start(void* aPack) { + auto* pack = static_cast<ThreadTrampoline<F, Args...>*>(aPack); + pack->callMain(std::index_sequence_for<Args...>{}); + js_delete(pack); + return 0; + } + + template <size_t... Indices> + void callMain(std::index_sequence<Indices...>) { + // Pretend createMutex is a semaphore and wait for a notification that the + // thread that spawned us is ready. + createMutex.lock(); + createMutex.unlock(); + f(std::move(std::get<Indices>(args))...); + } +}; + +} // namespace detail +} // namespace js + +#undef THREAD_RETURN_TYPE + +#endif // threading_Thread_h diff --git a/js/src/threading/ThreadId.h b/js/src/threading/ThreadId.h new file mode 100644 index 0000000000..1063d355f2 --- /dev/null +++ b/js/src/threading/ThreadId.h @@ -0,0 +1,38 @@ +/* -*- 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_ThreadId_h +#define threading_ThreadId_h + +namespace js { + +class ThreadId { + class PlatformData; + void* platformData_[2]; + + public: + ThreadId(); + + ThreadId(const ThreadId&) = default; + ThreadId(ThreadId&&) = default; + ThreadId& operator=(const ThreadId&) = default; + ThreadId& operator=(ThreadId&&) = default; + + bool operator==(const ThreadId& aOther) const; + bool operator!=(const ThreadId& aOther) const { return !operator==(aOther); } + + MOZ_IMPLICIT operator bool() const; + + inline PlatformData* platformData(); + inline const PlatformData* platformData() const; + + // Return the thread id of the calling thread. + static ThreadId ThisThreadId(); +}; + +} // namespace js + +#endif // threading_ThreadId_h diff --git a/js/src/threading/noop/CpuCount.cpp b/js/src/threading/noop/CpuCount.cpp new file mode 100644 index 0000000000..9be4d40415 --- /dev/null +++ b/js/src/threading/noop/CpuCount.cpp @@ -0,0 +1,9 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <unistd.h> +#include "threading/CpuCount.h" + +uint32_t js::GetCPUCount() { return 1; } diff --git a/js/src/threading/noop/NoopThread.cpp b/js/src/threading/noop/NoopThread.cpp new file mode 100644 index 0000000000..5cab9cb5b4 --- /dev/null +++ b/js/src/threading/noop/NoopThread.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "threading/noop/ThreadPlatformData.h" + +namespace js { + +inline ThreadId::PlatformData* ThreadId::platformData() { + return reinterpret_cast<PlatformData*>(platformData_); +} + +inline const ThreadId::PlatformData* ThreadId::platformData() const { + return reinterpret_cast<const PlatformData*>(platformData_); +} + +ThreadId::ThreadId() {} +ThreadId::operator bool() const { return false; } +bool ThreadId::operator==(const ThreadId& aOther) const { return true; } +bool Thread::create(void* (*aMain)(void*), void* aArg) { return false; } +void Thread::join() {} +void Thread::detach() {} +ThreadId ThreadId::ThisThreadId() { return ThreadId(); } +void ThisThread::SetName(const char*) {} +void ThisThread::GetName(char*, size_t) {} +void ThisThread::SleepMilliseconds(size_t) { + MOZ_CRASH("There is no any implementation for sleep."); +} + +} // namespace js diff --git a/js/src/threading/noop/ThreadPlatformData.h b/js/src/threading/noop/ThreadPlatformData.h new file mode 100644 index 0000000000..18159e718c --- /dev/null +++ b/js/src/threading/noop/ThreadPlatformData.h @@ -0,0 +1,24 @@ +/* -*- 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_noop_ThreadPlatformData_h +#define threading_noop_ThreadPlatformData_h + +#include <stdlib.h> +#include <string.h> + +#include "threading/Thread.h" + +namespace js { + +class ThreadId::PlatformData { + friend class Thread; + friend class ThreadId; +}; + +} // namespace js + +#endif // threading_noop_ThreadPlatformData_h diff --git a/js/src/threading/posix/CpuCount.cpp b/js/src/threading/posix/CpuCount.cpp new file mode 100644 index 0000000000..b8fe1d3f66 --- /dev/null +++ b/js/src/threading/posix/CpuCount.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <unistd.h> + +#include "threading/CpuCount.h" + +uint32_t js::GetCPUCount() { + static uint32_t ncpus = 0; + + // _SC_NPROCESSORS_CONF and _SC_NPROCESSORS_ONLN are common, but not + // standard. + if (ncpus == 0) { +#if defined(_SC_NPROCESSORS_CONF) + long n = sysconf(_SC_NPROCESSORS_CONF); + ncpus = (n > 0) ? uint32_t(n) : 1; +#elif defined(_SC_NPROCESSORS_ONLN) + long n = sysconf(_SC_NPROCESSORS_ONLN); + ncpus = (n > 0) ? uint32_t(n) : 1; +#else + ncpus = 1; +#endif + } + + return ncpus; +} diff --git a/js/src/threading/posix/PosixThread.cpp b/js/src/threading/posix/PosixThread.cpp new file mode 100644 index 0000000000..35532e375b --- /dev/null +++ b/js/src/threading/posix/PosixThread.cpp @@ -0,0 +1,146 @@ +/* -*- 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 <chrono> +#include <thread> + +#include "js/Utility.h" +#include "threading/posix/ThreadPlatformData.h" +#include "threading/Thread.h" + +namespace js { + +inline ThreadId::PlatformData* ThreadId::platformData() { + static_assert(sizeof platformData_ >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<PlatformData*>(platformData_); +} + +inline const ThreadId::PlatformData* ThreadId::platformData() const { + static_assert(sizeof platformData_ >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<const PlatformData*>(platformData_); +} + +ThreadId::ThreadId() { platformData()->hasThread = false; } + +ThreadId::operator bool() const { return platformData()->hasThread; } + +bool ThreadId::operator==(const ThreadId& 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)); +} + +bool Thread::create(void* (*aMain)(void*), void* aArg) { + MOZ_RELEASE_ASSERT(!joinable()); + + if (oom::ShouldFailWithOOM()) { + return false; + } + + 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) { + // On either Windows or POSIX we can't be sure if id_ was initialised. So + // reset it manually. + id_ = ThreadId(); + return false; + } + id_.platformData()->hasThread = true; + return true; +} + +void Thread::join() { + MOZ_RELEASE_ASSERT(joinable()); + int r = pthread_join(id_.platformData()->ptThread, nullptr); + MOZ_RELEASE_ASSERT(!r); + id_ = ThreadId(); +} + +void Thread::detach() { + MOZ_RELEASE_ASSERT(joinable()); + int r = pthread_detach(id_.platformData()->ptThread); + MOZ_RELEASE_ASSERT(!r); + id_ = ThreadId(); +} + +ThreadId ThreadId::ThisThreadId() { + ThreadId id; + id.platformData()->ptThread = pthread_self(); + id.platformData()->hasThread = true; + MOZ_RELEASE_ASSERT(id != ThreadId()); + return id; +} + +void ThisThread::SetName(const char* name) { + MOZ_RELEASE_ASSERT(name); + +#if (defined(__APPLE__) && defined(__MACH__)) || defined(__linux__) +# if defined(XP_DARWIN) + // Mac OS X has a length limit of 63 characters, but there is no API + // exposing it. +# define SETNAME_LENGTH_CONSTRAINT 63 +# else + // On linux the name may not be longer than 16 bytes, including + // the null terminator. Truncate the name to 15 characters. +# define SETNAME_LENGTH_CONSTRAINT 15 +# endif + char nameBuf[SETNAME_LENGTH_CONSTRAINT + 1]; + + 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 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(HAVE_PTHREAD_GET_NAME_NP) + pthread_get_name_np(pthread_self(), nameBuffer, len); + rv = 0; +#elif defined(__linux__) + rv = prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(nameBuffer)); +#endif + + if (rv) { + nameBuffer[0] = '\0'; + } +} + +void ThisThread::SleepMilliseconds(size_t ms) { + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +} + +} // namespace js diff --git a/js/src/threading/posix/ThreadPlatformData.h b/js/src/threading/posix/ThreadPlatformData.h new file mode 100644 index 0000000000..2ac197d771 --- /dev/null +++ b/js/src/threading/posix/ThreadPlatformData.h @@ -0,0 +1,42 @@ +/* -*- 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_posix_PlatformData_h +#define threading_posix_PlatformData_h + +#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" + +namespace js { + +class ThreadId::PlatformData { + friend class Thread; + friend class ThreadId; + 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; +}; + +} // namespace js + +#endif // threading_posix_PlatformData_h diff --git a/js/src/threading/windows/CpuCount.cpp b/js/src/threading/windows/CpuCount.cpp new file mode 100644 index 0000000000..39f8b49402 --- /dev/null +++ b/js/src/threading/windows/CpuCount.cpp @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/CpuCount.h" + +#include "util/WindowsWrapper.h" + +uint32_t js::GetCPUCount() { + static uint32_t ncpus = 0; + + if (ncpus == 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + ncpus = uint32_t(sysinfo.dwNumberOfProcessors); + } + + return ncpus; +} diff --git a/js/src/threading/windows/ThreadPlatformData.h b/js/src/threading/windows/ThreadPlatformData.h new file mode 100644 index 0000000000..72baa80cc6 --- /dev/null +++ b/js/src/threading/windows/ThreadPlatformData.h @@ -0,0 +1,30 @@ +/* -*- 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_windows_PlatformData_h +#define threading_windows_PlatformData_h + +#include <assert.h> +#include <new.h> +#include <process.h> + +#include "threading/Thread.h" + +#include "util/WindowsWrapper.h" + +namespace js { + +class ThreadId::PlatformData { + friend class Thread; + friend class ThreadId; + + HANDLE handle; + unsigned id; +}; + +} // namespace js + +#endif // threading_windows_PlatformData_h diff --git a/js/src/threading/windows/WindowsThread.cpp b/js/src/threading/windows/WindowsThread.cpp new file mode 100644 index 0000000000..68d4808ebd --- /dev/null +++ b/js/src/threading/windows/WindowsThread.cpp @@ -0,0 +1,127 @@ +/* -*- 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 <chrono> +#include <thread> + +#include "threading/Thread.h" +#include "threading/windows/ThreadPlatformData.h" + +namespace js { + +inline ThreadId::PlatformData* ThreadId::platformData() { + static_assert(sizeof platformData_ >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<PlatformData*>(platformData_); +} + +inline const ThreadId::PlatformData* ThreadId::platformData() const { + static_assert(sizeof platformData_ >= sizeof(PlatformData), + "platformData_ is too small"); + return reinterpret_cast<const PlatformData*>(platformData_); +} + +ThreadId::ThreadId() { + platformData()->handle = nullptr; + platformData()->id = 0; +} + +ThreadId::operator bool() const { return platformData()->handle; } + +bool ThreadId::operator==(const ThreadId& aOther) const { + return platformData()->id == aOther.platformData()->id; +} + +bool Thread::create(unsigned int(__stdcall* aMain)(void*), void* aArg) { + MOZ_RELEASE_ASSERT(!joinable()); + + if (oom::ShouldFailWithOOM()) { + return false; + } + + // 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) { + // On either Windows or POSIX we can't be sure if id_ was initalisad. So + // reset it manually. + id_ = ThreadId(); + return false; + } + id_.platformData()->handle = reinterpret_cast<HANDLE>(handle); + return true; +} + +void 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_ = ThreadId(); +} + +void Thread::detach() { + MOZ_RELEASE_ASSERT(joinable()); + BOOL success = CloseHandle(id_.platformData()->handle); + MOZ_RELEASE_ASSERT(success); + id_ = ThreadId(); +} + +ThreadId ThreadId::ThisThreadId() { + ThreadId id; + id.platformData()->handle = GetCurrentThread(); + id.platformData()->id = GetCurrentThreadId(); + MOZ_RELEASE_ASSERT(id != ThreadId()); + return id; +} + +void 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 // _MSC_VER +} + +void ThisThread::GetName(char* nameBuffer, size_t len) { + MOZ_RELEASE_ASSERT(len > 0); + *nameBuffer = '\0'; +} + +void ThisThread::SleepMilliseconds(size_t ms) { + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +} + +} // namespace js |