/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/objects/js-atomics-synchronization.h
361 строка
15 KB
Michaël Zasso
deps: update V8 to 14.6.202.33
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
f1e0b83
Код
Авторство
О чём код?
// Copyright 2022 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef V8_OBJECTS_JS_ATOMICS_SYNCHRONIZATION_H_ #define V8_OBJECTS_JS_ATOMICS_SYNCHRONIZATION_H_ #include <atomic> #include "src/base/platform/time.h" #include "src/execution/thread-id.h" #include "src/objects/contexts.h" #include "src/objects/js-objects.h" #include "src/objects/js-struct.h" #include "src/objects/struct.h" // Has to be the last include (doesn't have include guards): #include "src/objects/object-macros.h" namespace v8 { namespace internal { #include "torque-generated/src/objects/js-atomics-synchronization-tq.inc" namespace detail { class WaiterQueueLockGuard; class WaiterQueueNode; } // namespace detail using detail::WaiterQueueLockGuard; using detail::WaiterQueueNode; // JSSynchronizationPrimitive is the base class for JSAtomicsMutex and // JSAtomicsCondition. It contains a 32-bit state field and a pointer to a // waiter queue head, used to manage the queue of waiting threads for both: the // mutex and the condition variable. class JSSynchronizationPrimitive : public TorqueGeneratedJSSynchronizationPrimitive< JSSynchronizationPrimitive, AlwaysSharedSpaceJSObject> { public: // Synchronization only store raw data as state. static constexpr int kEndOfTaggedFieldsOffset = JSObject::kHeaderSize; class BodyDescriptor; static void IsolateDeinit(Isolate* isolate); Tagged<Object> NumWaitersForTesting(Isolate* requester); TQ_OBJECT_CONSTRUCTORS(JSSynchronizationPrimitive) inline void SetNullWaiterQueueHead(); protected: using StateT = uint32_t; // The `HasWaitersField` bitfield has the following properties: // - It isn't a lock bit, meaning that if this bit is 1, // that doesn't imply that some thread has exclusive write access to the // lock state. // - It is a metadata bit that's only written with the queue lock bit held. // - It is set iff the external pointer is non-null. // - It can be read without holding any lock bit. // - It allows for fast and threadsafe checking if there is a waiter, // as dereferencing the waiter queue should be done only when the // `IsWaiterQueueLockedField` bit is set. using HasWaitersField = base::BitField<bool, 0, 1>; // The `IsWaiterQueueLockedField` bitfield protects the waiter queue head from // concurrent modification. It is set through as CAS operation in a spinlock. using IsWaiterQueueLockedField = HasWaitersField::Next<bool, 1>; template <class T, int size> using NextBitField = IsWaiterQueueLockedField::Next<T, size>; inline std::atomic<StateT>* AtomicStatePtr(); inline WaiterQueueNode* DestructivelyGetWaiterQueueHead(Isolate* requester); // Store the waiter queue head in the synchronization primitive. If the head // is not null, the returned state has the kHasWaitersBit set. // In case of pointer compression, the waiter queue head is encoded as an // `ExternalPointerHandle`. inline StateT SetWaiterQueueHead(Isolate* requester, WaiterQueueNode* waiter_head, StateT new_state); // Set the new state without modifying bits outside the waiter queue mask. static void SetWaiterQueueStateOnly(std::atomic<StateT>* state, StateT new_state); static bool TryLockWaiterQueueExplicit(std::atomic<StateT>* state, StateT& expected); using TorqueGeneratedJSSynchronizationPrimitive< JSSynchronizationPrimitive, AlwaysSharedSpaceJSObject>::state; using TorqueGeneratedJSSynchronizationPrimitive< JSSynchronizationPrimitive, AlwaysSharedSpaceJSObject>::set_state; using DequeueMatcher = std::function<bool(WaiterQueueNode*)>; static constexpr StateT kEmptyState = 0; static constexpr StateT kWaiterQueueMask = base::BitFieldUnion<HasWaitersField, IsWaiterQueueLockedField>::kMask; private: friend class WaiterQueueLockGuard; #if V8_COMPRESS_POINTERS // When pointer compression is enabled, the pointer to the waiter queue head // is stored in the external pointer table and the object itself only contains // a 32-bit external pointer handles. inline ExternalPointerHandle* waiter_queue_head_handle_location() const; #else inline WaiterQueueNode** waiter_queue_head_location() const; #endif }; // A non-recursive mutex that is exposed to JS. // // It has the following properties: // - Slim: 12-16 bytes. Lock state is 4 bytes, waiter queue head is 4 bytes // when V8_COMPRESS_POINTERS, and sizeof(void*) otherwise. Owner thread is // an additional 4 bytes. // - Fast when uncontended: a single weak CAS. // - Possibly unfair under contention. // - Moving GC safe. It uses an index into the shared Isolate's external // pointer table to store a queue of sleeping threads. // - Parks the main thread LocalHeap when the thread is blocked on acquiring // the lock. Unparks the main thread LocalHeap when unblocked. This means // that the lock can only be used with main thread isolates (including // workers) but not with helper threads that have their own LocalHeap. // // This mutex manages its own queue of waiting threads under contention, i.e. // it implements a futex in userland. The algorithm is inspired by WebKit's // ParkingLot. // // The state variable encodes the locking state as a single word: 0bLQW. // - W: Whether there are waiter threads in the queue. // - Q: Whether the waiter queue is locked. // - L: Whether the lock itself is locked. // The locking algorithm is as follows: // 1. Fast Path. Unlocked+Uncontended(0b000) -> Locked+Uncontended(0b100). // 2. Otherwise, slow path. // a. Attempt to acquire the L bit (set current state | 0b100) on the state // using a CAS spin loop bounded to some number of iterations. // b. If L bit cannot be acquired, park the current thread: // i. Acquire the Q bit (set current state | 0b010) in a spinlock. // ii. Destructively get the waiter queue head. // iii. Enqueue this thread's WaiterQueueNode to the tail of the list // pointed to by the head, possibly creating a new list. // iv. Release the Q bit and set the W bit // (set (current state | 0b001) & ~0b010 in a single CAS operation). // iv. Put the thread to sleep. // v. Upon wake up, go to i. // The unlocking algorithm is as follows: // 1. Fast Path. Locked+Uncontended(0b100) -> Unlocked+Uncontended(0b000). // 2. Otherwise, slow path. // a. Acquire the Q bit (set current state | 0b010) in a spinlock. // b. Destructively get the waiter queue head. // c. If the head is not null, dequeue the head. // d. Store the new waiter queue head (possibly null). // f. If the list is empty, clear the W bit (set current state & ~0b001). // g. Release the Q bit and clear the L bit (set current state & ~0b100). // (The W and Q bits must be set in a single CAS operation). // h. If the list was not empty, notify the dequeued head. class JSAtomicsMutex : public TorqueGeneratedJSAtomicsMutex<JSAtomicsMutex, JSSynchronizationPrimitive> { public: // A non-copyable wrapper class that provides an RAII-style mechanism for // owning the `JSAtomicsMutex`. class V8_NODISCARD LockGuardBase { public: LockGuardBase(const LockGuardBase&) = delete; LockGuardBase& operator=(const LockGuardBase&) = delete; inline ~LockGuardBase(); bool locked() const { return locked_; } protected: inline LockGuardBase(Isolate* isolate, DirectHandle<JSAtomicsMutex> mutex, bool locked); private: Isolate* isolate_; DirectHandle<JSAtomicsMutex> mutex_; bool locked_; }; // The mutex is attempted to be locked via `Lock` when a `LockGuard` // object is created, the lock will be acquired unless the timeout is reached. // If the mutex was acquired, then it is released when the `LockGuard` object // is destructed. class V8_NODISCARD LockGuard final : public LockGuardBase { public: inline LockGuard(Isolate* isolate, DirectHandle<JSAtomicsMutex> mutex, std::optional<base::TimeDelta> timeout = std::nullopt); }; // The mutex is attempted to be locked via `TryLock` when a `TryLockGuard` // object is created. If the mutex was acquired, then it is released when the // `TryLockGuard` object is destructed. class V8_NODISCARD TryLockGuard final : public LockGuardBase { public: inline TryLockGuard(Isolate* isolate, DirectHandle<JSAtomicsMutex> mutex); }; DECL_PRINTER(JSAtomicsMutex) EXPORT_DECL_VERIFIER(JSAtomicsMutex) // Lock the mutex, blocking if it's currently owned by another thread. // Returns false if the lock times out, true otherwise. static inline bool Lock( Isolate* requester, DirectHandle<JSAtomicsMutex> mutex, std::optional<base::TimeDelta> timeout = std::nullopt); V8_WARN_UNUSED_RESULT inline bool TryLock(); inline void Unlock(Isolate* requester); inline bool IsHeld(); inline bool IsCurrentThreadOwner(); V8_EXPORT_PRIVATE static DirectHandle<JSObject> CreateResultObject( Isolate* isolate, DirectHandle<Object> value, bool success); TQ_OBJECT_CONSTRUCTORS(JSAtomicsMutex) private: friend class Factory; // There are 3 state bits: whether there are waiter threads in the queue, // whether the waiter queue is locked (both inherited from the base class), // and whether the lock itself is locked (IsLockedField). using IsLockedField = JSSynchronizationPrimitive::NextBitField<bool, 1>; static constexpr StateT kUnlockedUncontended = kEmptyState; static constexpr StateT kLockedUncontended = IsLockedField::encode(true); inline void SetCurrentThreadAsOwner(); inline void ClearOwnerThread(); inline std::atomic<int32_t>* AtomicOwnerThreadIdPtr(); V8_EXPORT_PRIVATE static bool LockSlowPath( Isolate* requester, DirectHandle<JSAtomicsMutex> mutex, std::atomic<StateT>* state, std::optional<base::TimeDelta> timeout); V8_EXPORT_PRIVATE void UnlockSlowPath(Isolate* requester, std::atomic<StateT>* state); // Returns true if the JS mutex was taken and false otherwise. bool LockJSMutexOrDequeueTimedOutWaiter(Isolate* requester, std::atomic<StateT>* state, WaiterQueueNode* timed_out_waiter); static bool TryLockExplicit(std::atomic<StateT>* state, StateT& expected); // Returns nullopt if the JS mutex is acquired, otherwise return an optional // with a `WaiterQueueLockGuard` object. static std::optional<WaiterQueueLockGuard> LockWaiterQueueOrJSMutex( std::atomic<StateT>* state, StateT& current_state); V8_EXPORT_PRIVATE static bool MutexTryLock(Isolate* requester, DirectHandle<JSAtomicsMutex> mutex, std::atomic<StateT>* state); V8_INLINE static bool BackoffTryLock(Isolate* requester, DirectHandle<JSAtomicsMutex> mutex, std::atomic<StateT>* state); V8_EXPORT_PRIVATE static bool MaybeEnqueueNode( Isolate* requester, DirectHandle<JSAtomicsMutex> mutex, std::atomic<StateT>* state, WaiterQueueNode* this_waiter); template <typename LockSlowPathWrapper, typename = std::enable_if<std::is_invocable_r_v< bool, LockSlowPathWrapper, std::atomic<StateT>*>>> static inline bool LockImpl(Isolate* requester, DirectHandle<JSAtomicsMutex> mutex, std::optional<base::TimeDelta> timeout, LockSlowPathWrapper slow_path_wrapper); using TorqueGeneratedJSAtomicsMutex< JSAtomicsMutex, JSSynchronizationPrimitive>::owner_thread_id; using TorqueGeneratedJSAtomicsMutex< JSAtomicsMutex, JSSynchronizationPrimitive>::set_owner_thread_id; }; // A condition variable that is exposed to JS. // // It has the following properties: // - Slim: 8-12 bytes. Lock state is 4 bytes, waiter queue head is 4 bytes // when V8_COMPRESS_POINTERS, and sizeof(void*) otherwise. // - Moving GC safe. It uses an index into the shared Isolate's external // pointer table to store a queue of sleeping threads. // - Parks the main thread LocalHeap when waiting. Unparks the main thread // LocalHeap after waking up. // // This condition variable manages its own queue of waiting threads, like // JSAtomicsMutex. The algorithm is inspired by WebKit's ParkingLot. // // The state variable encodes the locking state as a single word: 0bQW. // - W: Whether there are waiter threads in the queue. // - Q: Whether the waiter queue is locked. // // The waiting algorithm is as follows: // 1. Acquire the Q bit (set current state | 0b010) in a spinlock. // 2. Destructively get the waiter queue head. // 3. Enqueue this thread's WaiterQueueNode to the tail of the list pointed to // by the head, possibly creating a new list. // 4. Release the Q bit and set the W bit (set (current state | 0b001) & ~0b010 // in a single CAS operation). // 5. Put the thread to sleep. // // The notification algorithm is as follows: // 1. Acquire the Q bit (set current state | 0b010) in a spinlock. // 2. Destructively get the waiter queue head. // 3. If the head is not null, dequeue the head. // 4. Store the new waiter queue head (possibly null). // 5. If the list is empty, clear the W bit (set current state & ~0b001). // 6. Release the Q bit (set current state & ~0b010). // (The W and Q bits must be set in a single CAS operation). // 7. If the list was not empty, notify the dequeued head. class JSAtomicsCondition : public TorqueGeneratedJSAtomicsCondition<JSAtomicsCondition, JSSynchronizationPrimitive> { public: DECL_PRINTER(JSAtomicsCondition) EXPORT_DECL_VERIFIER(JSAtomicsCondition) V8_EXPORT_PRIVATE static bool WaitFor(Isolate* requester, DirectHandle<JSAtomicsCondition> cv, DirectHandle<JSAtomicsMutex> mutex, std::optional<base::TimeDelta> timeout); static constexpr uint32_t kAllWaiters = UINT32_MAX; // Notify {count} waiters. Returns the number of waiters woken up. static V8_EXPORT_PRIVATE uint32_t Notify(Isolate* requester, DirectHandle<JSAtomicsCondition> cv, uint32_t count); TQ_OBJECT_CONSTRUCTORS(JSAtomicsCondition) private: friend class Factory; static void QueueWaiter(Isolate* requester, DirectHandle<JSAtomicsCondition> cv, WaiterQueueNode* waiter); using DequeueAction = std::function<uint32_t(WaiterQueueNode**)>; static uint32_t DequeueExplicit(Isolate* requester, DirectHandle<JSAtomicsCondition> cv, std::atomic<StateT>* state, const DequeueAction& dequeue_action); }; } // namespace internal } // namespace v8 #include "src/objects/object-macros-undef.h" #endif // V8_OBJECTS_JS_ATOMICS_SYNCHRONIZATION_H_