// 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 <map> #include "include/v8.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/base/platform/time.h" #include "src/tasks/cancelable-task.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 { class BackingStore; class FutexWaitList; 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: // Create a sync FutexWaitListNode. FutexWaitListNode() = default; // Create an async FutexWaitListNode. FutexWaitListNode(const std::shared_ptr<BackingStore>& backing_store, size_t wait_addr, Handle<JSObject> promise_capability, Isolate* isolate); ~FutexWaitListNode(); void NotifyWake(); bool IsAsync() const { return isolate_for_async_waiters_ != nullptr; } // Returns false if the cancelling failed, true otherwise. bool CancelTimeoutTask(); class ResetWaitingOnScopeExit { public: explicit ResetWaitingOnScopeExit(FutexWaitListNode* node) : node_(node) {} ~ResetWaitingOnScopeExit() { node_->waiting_ = false; } private: FutexWaitListNode* node_; DISALLOW_COPY_AND_ASSIGN(ResetWaitingOnScopeExit); }; private: friend class FutexEmulation; friend class FutexWaitList; // Set only for async FutexWaitListNodes. Isolate* isolate_for_async_waiters_ = nullptr; std::shared_ptr<TaskRunner> task_runner_; CancelableTaskManager* cancelable_task_manager_ = nullptr; base::ConditionVariable cond_; // prev_ and next_ are protected by FutexEmulation::mutex_. FutexWaitListNode* prev_ = nullptr; FutexWaitListNode* next_ = nullptr; std::weak_ptr<BackingStore> backing_store_; size_t wait_addr_ = 0; // The memory location the FutexWaitListNode is waiting on. Equals // backing_store_->buffer_start() + wait_addr_ at FutexWaitListNode creation // time. Storing the wait_location_ separately is needed, since we can't // necessarily reconstruct it, because the BackingStore might get deleted // while the FutexWaitListNode is still alive. FutexWaitListNode must know its // wait location, since they are stored in per-location lists, and to remove // the node, we need to be able to find the list it's on (to be able to // update the head and tail of the list). int8_t* wait_location_ = nullptr; // 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_ = false; bool interrupted_ = false; // Only for async FutexWaitListNodes. Weak Global handle. Must not be // synchronously resolved by a non-owner Isolate. v8::Global<v8::Promise> promise_; // Only for async FutexWaitListNodes. Weak Global handle. v8::Global<v8::Context> native_context_; // Only for async FutexWaitListNodes. If async_timeout_time_ is // base::TimeTicks(), this async waiter doesn't have a timeout or has already // been notified. Values other than base::TimeTicks() are used for async // waiters with an active timeout. base::TimeTicks async_timeout_time_; CancelableTaskManager::Id timeout_task_id_ = CancelableTaskManager::kInvalidTaskId; DISALLOW_COPY_AND_ASSIGN(FutexWaitListNode); }; class FutexEmulation : public AllStatic { public: enum WaitMode { kSync = 0, kAsync }; // 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 WaitJs32(Isolate* isolate, WaitMode mode, Handle<JSArrayBuffer> array_buffer, size_t addr, int32_t value, double rel_timeout_ms); // An version of WaitJs32 for int64_t values. static Object WaitJs64(Isolate* isolate, WaitMode mode, Handle<JSArrayBuffer> array_buffer, size_t addr, int64_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. V8_EXPORT_PRIVATE static Object WaitWasm32(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, size_t addr, int32_t value, int64_t rel_timeout_ns); // Same as Wait32 above except it checks for an int64_t value in the // array_buffer. V8_EXPORT_PRIVATE static Object WaitWasm64(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, size_t addr, int64_t value, int64_t rel_timeout_ns); // 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. V8_EXPORT_PRIVATE static Object Wake(Handle<JSArrayBuffer> array_buffer, size_t addr, uint32_t num_waiters_to_wake); // Called before |isolate| dies. Removes async waiters owned by |isolate|. static void IsolateDeinit(Isolate* isolate); // Return the number of threads or async waiters waiting on |addr|. Should // only be used for testing. static Object NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer, size_t addr); // Return the number of async waiters (which belong to |isolate|) waiting. // Should only be used for testing. static Object NumAsyncWaitersForTesting(Isolate* isolate); // Return the number of async waiters which were waiting for |addr| and are // now waiting for the Promises to be resolved. Should only be used for // testing. static Object NumUnresolvedAsyncPromisesForTesting( Handle<JSArrayBuffer> array_buffer, size_t addr); private: friend class FutexWaitListNode; friend class AtomicsWaitWakeHandle; friend class ResolveAsyncWaiterPromisesTask; friend class AsyncWaiterTimeoutTask; template <typename T> static Object Wait(Isolate* isolate, WaitMode mode, Handle<JSArrayBuffer> array_buffer, size_t addr, T value, double rel_timeout_ms); template <typename T> static Object Wait(Isolate* isolate, WaitMode mode, Handle<JSArrayBuffer> array_buffer, size_t addr, T value, bool use_timeout, int64_t rel_timeout_ns); template <typename T> static Object WaitSync(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, size_t addr, T value, bool use_timeout, int64_t rel_timeout_ns); template <typename T> static Object WaitAsync(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, size_t addr, T value, bool use_timeout, int64_t rel_timeout_ns); // Resolve the Promises of the async waiters which belong to |isolate|. static void ResolveAsyncWaiterPromises(Isolate* isolate); static void ResolveAsyncWaiterPromise(FutexWaitListNode* node); static void HandleAsyncWaiterTimeout(FutexWaitListNode* node); static void NotifyAsyncWaiter(FutexWaitListNode* node); // Remove the node's Promise from the NativeContext's Promise set. static void CleanupAsyncWaiterPromise(FutexWaitListNode* node); }; } // namespace internal } // namespace v8 #endif // V8_EXECUTION_FUTEX_EMULATION_H_