summaryrefslogtreecommitdiffstats
path: root/js/src/threading
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/threading/ConditionVariable.h139
-rw-r--r--js/src/threading/CpuCount.h16
-rw-r--r--js/src/threading/ExclusiveData.h225
-rw-r--r--js/src/threading/LockGuard.h43
-rw-r--r--js/src/threading/Mutex.cpp75
-rw-r--r--js/src/threading/Mutex.h92
-rw-r--r--js/src/threading/ProtectedData.cpp150
-rw-r--r--js/src/threading/ProtectedData.h429
-rw-r--r--js/src/threading/Thread.cpp32
-rw-r--r--js/src/threading/Thread.h226
-rw-r--r--js/src/threading/ThreadId.h38
-rw-r--r--js/src/threading/posix/CpuCount.cpp28
-rw-r--r--js/src/threading/posix/PosixThread.cpp132
-rw-r--r--js/src/threading/posix/ThreadPlatformData.h42
-rw-r--r--js/src/threading/windows/CpuCount.cpp20
-rw-r--r--js/src/threading/windows/ThreadPlatformData.h30
-rw-r--r--js/src/threading/windows/WindowsThread.cpp120
17 files changed, 1837 insertions, 0 deletions
diff --git a/js/src/threading/ConditionVariable.h b/js/src/threading/ConditionVariable.h
new file mode 100644
index 0000000000..ba83abde47
--- /dev/null
+++ b/js/src/threading/ConditionVariable.h
@@ -0,0 +1,139 @@
+/* -*- 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/PlatformConditionVariable.h"
+#include "mozilla/TimeStamp.h"
+
+#include <stdint.h>
+#include <utility>
+#ifndef XP_WIN
+# 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..c43cd1797c
--- /dev/null
+++ b/js/src/threading/ExclusiveData.h
@@ -0,0 +1,225 @@
+/* -*- 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_;
+ 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 {
+ 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(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();
+ }
+ }
+ };
+
+ /**
+ * Access the protected inner `T` value for exclusive reading and writing.
+ */
+ Guard lock() const { return Guard(*this); }
+};
+
+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); }
+};
+
+} // 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..216ee42bd5
--- /dev/null
+++ b/js/src/threading/Mutex.cpp
@@ -0,0 +1,75 @@
+/* -*- 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();
+}
+
+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..f4bc8efe93
--- /dev/null
+++ b/js/src/threading/Mutex.h
@@ -0,0 +1,92 @@
+/* -*- 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();
+ void unlock();
+ void assertOwnedByCurrentThread() const;
+#else
+ static bool Init() { return true; }
+
+ explicit Mutex(const MutexId& id) {}
+
+ void lock() { impl_.lock(); }
+ 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..94c65a259a
--- /dev/null
+++ b/js/src/threading/ProtectedData.cpp
@@ -0,0 +1,150 @@
+/* -*- 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 "gc/Zone.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) {
+ if (CurrentThreadIsIonCompiling()) {
+ return true;
+ }
+ }
+
+ if (Helper == AllowedHelperThread::GCTask ||
+ Helper == AllowedHelperThread::GCTaskOrIonCompile) {
+ JSContext* cx = TlsContext.get();
+ if (cx->defaultFreeOp()->isCollecting()) {
+ 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::IonCompile>;
+
+template <AllowedHelperThread Helper>
+void CheckZone<Helper>::check() const {
+ if (OnHelperThread<Helper>()) {
+ return;
+ }
+
+ if (zone->usedByHelperThread()) {
+ // This may only be accessed by the helper thread using this zone.
+ MOZ_ASSERT(zone->ownedByCurrentHelperThread());
+ } else {
+ // The main thread is permitted access to all zones. These accesses
+ // are threadsafe if the zone is not in use by a helper thread.
+ MOZ_ASSERT(CurrentThreadCanAccessRuntime(TlsContext.get()->runtime()));
+ }
+}
+
+template class CheckZone<AllowedHelperThread::None>;
+template class CheckZone<AllowedHelperThread::GCTask>;
+template class CheckZone<AllowedHelperThread::IonCompile>;
+template class CheckZone<AllowedHelperThread::GCTaskOrIonCompile>;
+
+template <GlobalLock Lock, AllowedHelperThread Helper>
+void CheckGlobalLock<Lock, Helper>::check() const {
+ if (OnHelperThread<Helper>()) {
+ return;
+ }
+
+ switch (Lock) {
+ case GlobalLock::GCLock:
+ TlsContext.get()->runtime()->gc.assertCurrentThreadHasLockedGC();
+ break;
+ case GlobalLock::ScriptDataLock:
+ TlsContext.get()->runtime()->assertCurrentThreadHasScriptDataAccess();
+ break;
+ case GlobalLock::HelperThreadLock:
+ gHelperThreadLock.assertOwnedByCurrentThread();
+ break;
+ }
+}
+
+template class CheckGlobalLock<GlobalLock::GCLock, AllowedHelperThread::None>;
+template class CheckGlobalLock<GlobalLock::ScriptDataLock,
+ AllowedHelperThread::None>;
+template class CheckGlobalLock<GlobalLock::HelperThreadLock,
+ AllowedHelperThread::None>;
+
+template <AllowedHelperThread Helper>
+void CheckArenaListAccess<Helper>::check() const {
+ MOZ_ASSERT(zone);
+
+ if (OnHelperThread<Helper>()) {
+ return;
+ }
+
+ JSRuntime* rt = TlsContext.get()->runtime();
+ if (zone->isAtomsZone()) {
+ // The main thread can access the atoms arenas if it holds all the atoms
+ // table locks.
+ if (rt->currentThreadHasAtomsTableAccess()) {
+ return;
+ }
+
+ // Otherwise we must hold the GC lock if parallel parsing is running.
+ if (rt->isOffThreadParseRunning()) {
+ rt->gc.assertCurrentThreadHasLockedGC();
+ }
+ return;
+ }
+
+ CheckZone<AllowedHelperThread::None>::check();
+}
+
+template class CheckArenaListAccess<AllowedHelperThread::GCTask>;
+
+#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..4f5f928de3
--- /dev/null
+++ b/js/src/threading/ProtectedData.h
@@ -0,0 +1,429 @@
+/* -*- 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 {
+class JS_PUBLIC_API Zone;
+}
+
+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 { \
+ return ref() OP static_cast<T>(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 Zone constructor
+// argument.
+template <typename Check, typename T>
+class ProtectedDataZoneArg : public ProtectedData<Check, T> {
+ using Base = ProtectedData<Check, T>;
+
+ public:
+ template <typename... Args>
+ explicit ProtectedDataZoneArg(JS::Zone* zone, Args&&... args)
+ : ProtectedData<Check, T>(Check(zone), 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 even though they do not have exclusive access to any zone.
+enum class AllowedHelperThread { None, GCTask, IonCompile, GCTaskOrIonCompile };
+
+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 <AllowedHelperThread Helper>
+class CheckZone {
+#ifdef JS_HAS_PROTECTED_DATA_CHECKS
+ protected:
+ JS::Zone* zone;
+
+ public:
+ explicit CheckZone(JS::Zone* zone) : zone(zone) {}
+ void check() const;
+#else
+ public:
+ explicit CheckZone(JS::Zone* zone) {}
+#endif
+};
+
+// Data which may only be accessed by threads with exclusive access to the
+// associated zone, or by the runtime's main thread for zones which are not in
+// use by a helper thread.
+template <typename T>
+using ZoneData = ProtectedDataZoneArg<CheckZone<AllowedHelperThread::None>, T>;
+
+// Data which may only be accessed by threads with exclusive access to the
+// associated zone, or by various helper thread tasks.
+template <typename T>
+using ZoneOrGCTaskData =
+ ProtectedDataZoneArg<CheckZone<AllowedHelperThread::GCTask>, T>;
+template <typename T>
+using ZoneOrIonCompileData =
+ ProtectedDataZoneArg<CheckZone<AllowedHelperThread::IonCompile>, T>;
+template <typename T>
+using ZoneOrGCTaskOrIonCompileData =
+ ProtectedDataZoneArg<CheckZone<AllowedHelperThread::GCTaskOrIonCompile>, T>;
+
+// Runtime wide locks which might protect some data.
+enum class GlobalLock { GCLock, ScriptDataLock, 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 script data lock.
+template <typename T>
+using ScriptDataLockData = ProtectedDataNoCheckArgs<
+ CheckGlobalLock<GlobalLock::ScriptDataLock, 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>;
+
+// Custom check for arena list data that requires the GC lock to be held when
+// accessing the atoms zone if parallel parsing is running, in addition to the
+// usual Zone checks.
+template <AllowedHelperThread Helper>
+class CheckArenaListAccess : public CheckZone<AllowedHelperThread::None> {
+#ifdef JS_HAS_PROTECTED_DATA_CHECKS
+ public:
+ explicit CheckArenaListAccess(JS::Zone* zone)
+ : CheckZone<AllowedHelperThread::None>(zone) {}
+ void check() const;
+#else
+ public:
+ explicit CheckArenaListAccess(JS::Zone* zone)
+ : CheckZone<AllowedHelperThread::None>(zone) {}
+#endif
+};
+
+template <typename T>
+using ArenaListData =
+ ProtectedDataZoneArg<CheckArenaListAccess<AllowedHelperThread::GCTask>, 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..5632311165
--- /dev/null
+++ b/js/src/threading/Thread.h
@@ -0,0 +1,226 @@
+/* -*- 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/TimeStamp.h"
+#include "mozilla/Tuple.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>
+ MOZ_MUST_USE 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.
+ MOZ_MUST_USE 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);
+
+} // 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<std::decay_t<Args>...> args;
+
+ // Protect the thread id during creation.
+ Mutex createMutex;
+
+ // 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(mozilla::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/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..fb8088ba84
--- /dev/null
+++ b/js/src/threading/posix/PosixThread.cpp
@@ -0,0 +1,132 @@
+/* -*- 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 "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__)
+ // 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 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';
+ }
+}
+
+} // 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..8ec83dd7e3
--- /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/Windows.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..deaa578def
--- /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/Windows.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..ee5f9428aa
--- /dev/null
+++ b/js/src/threading/windows/WindowsThread.cpp
@@ -0,0 +1,120 @@
+/* -*- 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 "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';
+}
+
+} // namespace js