diff options
Diffstat (limited to 'js/src/threading/ProtectedData.h')
-rw-r--r-- | js/src/threading/ProtectedData.h | 371 |
1 files changed, 371 insertions, 0 deletions
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 |