| // Copyright 2015 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_EXECUTION_FUTEX_EMULATION_H_ |
| #define V8_EXECUTION_FUTEX_EMULATION_H_ |
| |
| #include <stdint.h> |
| |
| #include "src/base/atomicops.h" |
| #include "src/base/lazy-instance.h" |
| #include "src/base/macros.h" |
| #include "src/base/platform/condition-variable.h" |
| #include "src/base/platform/mutex.h" |
| #include "src/utils/allocation.h" |
| |
| // Support for emulating futexes, a low-level synchronization primitive. They |
| // are natively supported by Linux, but must be emulated for other platforms. |
| // This library emulates them on all platforms using mutexes and condition |
| // variables for consistency. |
| // |
| // This is used by the Futex API defined in the SharedArrayBuffer draft spec, |
| // found here: https://github.com/tc39/ecmascript_sharedmem |
| |
| namespace v8 { |
| |
| namespace base { |
| class TimeDelta; |
| } // namespace base |
| |
| namespace internal { |
| |
| template <typename T> |
| class Handle; |
| class Isolate; |
| class JSArrayBuffer; |
| |
| class AtomicsWaitWakeHandle { |
| public: |
| explicit AtomicsWaitWakeHandle(Isolate* isolate) : isolate_(isolate) {} |
| |
| void Wake(); |
| inline bool has_stopped() const { return stopped_; } |
| |
| private: |
| Isolate* isolate_; |
| bool stopped_ = false; |
| }; |
| |
| class FutexWaitListNode { |
| public: |
| FutexWaitListNode() |
| : prev_(nullptr), |
| next_(nullptr), |
| backing_store_(nullptr), |
| wait_addr_(0), |
| waiting_(false), |
| interrupted_(false) {} |
| |
| void NotifyWake(); |
| |
| private: |
| friend class FutexEmulation; |
| friend class FutexWaitList; |
| friend class ResetWaitingOnScopeExit; |
| |
| base::ConditionVariable cond_; |
| // prev_ and next_ are protected by FutexEmulation::mutex_. |
| FutexWaitListNode* prev_; |
| FutexWaitListNode* next_; |
| void* backing_store_; |
| size_t wait_addr_; |
| // waiting_ and interrupted_ are protected by FutexEmulation::mutex_ |
| // if this node is currently contained in FutexEmulation::wait_list_ |
| // or an AtomicsWaitWakeHandle has access to it. |
| bool waiting_; |
| bool interrupted_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FutexWaitListNode); |
| }; |
| |
| class FutexWaitList { |
| public: |
| FutexWaitList(); |
| |
| void AddNode(FutexWaitListNode* node); |
| void RemoveNode(FutexWaitListNode* node); |
| |
| private: |
| friend class FutexEmulation; |
| |
| FutexWaitListNode* head_; |
| FutexWaitListNode* tail_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FutexWaitList); |
| }; |
| |
| class ResetWaitingOnScopeExit { |
| public: |
| explicit ResetWaitingOnScopeExit(FutexWaitListNode* node) : node_(node) {} |
| ~ResetWaitingOnScopeExit() { node_->waiting_ = false; } |
| |
| private: |
| FutexWaitListNode* node_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ResetWaitingOnScopeExit); |
| }; |
| |
| class FutexEmulation : public AllStatic { |
| public: |
| // Pass to Wake() to wake all waiters. |
| static const uint32_t kWakeAll = UINT32_MAX; |
| |
| // Check that array_buffer[addr] == value, and return "not-equal" if not. If |
| // they are equal, block execution on |isolate|'s thread until woken via |
| // |Wake|, or when the time given in |rel_timeout_ms| elapses. Note that |
| // |rel_timeout_ms| can be Infinity. |
| // If woken, return "ok", otherwise return "timed-out". The initial check and |
| // the decision to wait happen atomically. |
| static Object WaitJs(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, |
| size_t addr, int32_t value, double rel_timeout_ms); |
| |
| // Same as WaitJs above except it returns 0 (ok), 1 (not equal) and 2 (timed |
| // out) as expected by Wasm. |
| static Object Wait32(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, |
| size_t addr, int32_t value, double rel_timeout_ms); |
| |
| // Same as Wait32 above except it checks for an int64_t value in the |
| // array_buffer. |
| static Object Wait64(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, |
| size_t addr, int64_t value, double rel_timeout_ms); |
| |
| // Wake |num_waiters_to_wake| threads that are waiting on the given |addr|. |
| // |num_waiters_to_wake| can be kWakeAll, in which case all waiters are |
| // woken. The rest of the waiters will continue to wait. The return value is |
| // the number of woken waiters. |
| static Object Wake(Handle<JSArrayBuffer> array_buffer, size_t addr, |
| uint32_t num_waiters_to_wake); |
| |
| // Return the number of threads waiting on |addr|. Should only be used for |
| // testing. |
| static Object NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer, |
| size_t addr); |
| |
| private: |
| friend class FutexWaitListNode; |
| friend class AtomicsWaitWakeHandle; |
| |
| template <typename T> |
| static Object Wait(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, |
| size_t addr, T value, double rel_timeout_ms); |
| |
| // `mutex_` protects the composition of `wait_list_` (i.e. no elements may be |
| // added or removed without holding this mutex), as well as the `waiting_` |
| // and `interrupted_` fields for each individual list node that is currently |
| // part of the list. It must be the mutex used together with the `cond_` |
| // condition variable of such nodes. |
| static base::LazyMutex mutex_; |
| static base::LazyInstance<FutexWaitList>::type wait_list_; |
| }; |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_EXECUTION_FUTEX_EMULATION_H_ |