1 // Copyright 2015 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef V8_EXECUTION_FUTEX_EMULATION_H_ 6 #define V8_EXECUTION_FUTEX_EMULATION_H_ 7 8 #include <stdint.h> 9 10 #include "include/v8-persistent-handle.h" 11 #include "src/base/atomicops.h" 12 #include "src/base/lazy-instance.h" 13 #include "src/base/macros.h" 14 #include "src/base/platform/condition-variable.h" 15 #include "src/base/platform/mutex.h" 16 #include "src/base/platform/time.h" 17 #include "src/tasks/cancelable-task.h" 18 #include "src/utils/allocation.h" 19 20 // Support for emulating futexes, a low-level synchronization primitive. They 21 // are natively supported by Linux, but must be emulated for other platforms. 22 // This library emulates them on all platforms using mutexes and condition 23 // variables for consistency. 24 // 25 // This is used by the Futex API defined in the SharedArrayBuffer draft spec, 26 // found here: https://github.com/tc39/ecmascript_sharedmem 27 28 namespace v8 { 29 30 class Promise; 31 32 namespace base { 33 class TimeDelta; 34 } // namespace base 35 36 namespace internal { 37 38 class BackingStore; 39 class FutexWaitList; 40 41 template <typename T> 42 class Handle; 43 class Isolate; 44 class JSArrayBuffer; 45 46 class AtomicsWaitWakeHandle { 47 public: AtomicsWaitWakeHandle(Isolate * isolate)48 explicit AtomicsWaitWakeHandle(Isolate* isolate) : isolate_(isolate) {} 49 50 void Wake(); has_stopped()51 inline bool has_stopped() const { return stopped_; } 52 53 private: 54 Isolate* isolate_; 55 bool stopped_ = false; 56 }; 57 58 class FutexWaitListNode { 59 public: 60 // Create a sync FutexWaitListNode. 61 FutexWaitListNode() = default; 62 63 // Create an async FutexWaitListNode. 64 FutexWaitListNode(const std::shared_ptr<BackingStore>& backing_store, 65 size_t wait_addr, Handle<JSObject> promise_capability, 66 Isolate* isolate); 67 ~FutexWaitListNode(); 68 FutexWaitListNode(const FutexWaitListNode&) = delete; 69 FutexWaitListNode& operator=(const FutexWaitListNode&) = delete; 70 71 void NotifyWake(); 72 IsAsync()73 bool IsAsync() const { return isolate_for_async_waiters_ != nullptr; } 74 75 // Returns false if the cancelling failed, true otherwise. 76 bool CancelTimeoutTask(); 77 78 class V8_NODISCARD ResetWaitingOnScopeExit { 79 public: ResetWaitingOnScopeExit(FutexWaitListNode * node)80 explicit ResetWaitingOnScopeExit(FutexWaitListNode* node) : node_(node) {} ~ResetWaitingOnScopeExit()81 ~ResetWaitingOnScopeExit() { node_->waiting_ = false; } 82 ResetWaitingOnScopeExit(const ResetWaitingOnScopeExit&) = delete; 83 ResetWaitingOnScopeExit& operator=(const ResetWaitingOnScopeExit&) = delete; 84 85 private: 86 FutexWaitListNode* node_; 87 }; 88 89 private: 90 friend class FutexEmulation; 91 friend class FutexWaitList; 92 93 // Set only for async FutexWaitListNodes. 94 Isolate* isolate_for_async_waiters_ = nullptr; 95 std::shared_ptr<TaskRunner> task_runner_; 96 CancelableTaskManager* cancelable_task_manager_ = nullptr; 97 98 base::ConditionVariable cond_; 99 // prev_ and next_ are protected by FutexEmulation::mutex_. 100 FutexWaitListNode* prev_ = nullptr; 101 FutexWaitListNode* next_ = nullptr; 102 103 std::weak_ptr<BackingStore> backing_store_; 104 size_t wait_addr_ = 0; 105 106 // The memory location the FutexWaitListNode is waiting on. Equals 107 // backing_store_->buffer_start() + wait_addr_ at FutexWaitListNode creation 108 // time. Storing the wait_location_ separately is needed, since we can't 109 // necessarily reconstruct it, because the BackingStore might get deleted 110 // while the FutexWaitListNode is still alive. FutexWaitListNode must know its 111 // wait location, since they are stored in per-location lists, and to remove 112 // the node, we need to be able to find the list it's on (to be able to 113 // update the head and tail of the list). 114 int8_t* wait_location_ = nullptr; 115 116 // waiting_ and interrupted_ are protected by FutexEmulation::mutex_ 117 // if this node is currently contained in FutexEmulation::wait_list_ 118 // or an AtomicsWaitWakeHandle has access to it. 119 bool waiting_ = false; 120 bool interrupted_ = false; 121 122 // Only for async FutexWaitListNodes. Weak Global handle. Must not be 123 // synchronously resolved by a non-owner Isolate. 124 v8::Global<v8::Promise> promise_; 125 126 // Only for async FutexWaitListNodes. Weak Global handle. 127 v8::Global<v8::Context> native_context_; 128 129 // Only for async FutexWaitListNodes. If async_timeout_time_ is 130 // base::TimeTicks(), this async waiter doesn't have a timeout or has already 131 // been notified. Values other than base::TimeTicks() are used for async 132 // waiters with an active timeout. 133 base::TimeTicks async_timeout_time_; 134 135 CancelableTaskManager::Id timeout_task_id_ = 136 CancelableTaskManager::kInvalidTaskId; 137 }; 138 139 class FutexEmulation : public AllStatic { 140 public: 141 enum WaitMode { kSync = 0, kAsync }; 142 enum class CallType { kIsNotWasm = 0, kIsWasm }; 143 144 // Pass to Wake() to wake all waiters. 145 static const uint32_t kWakeAll = UINT32_MAX; 146 147 // Check that array_buffer[addr] == value, and return "not-equal" if not. If 148 // they are equal, block execution on |isolate|'s thread until woken via 149 // |Wake|, or when the time given in |rel_timeout_ms| elapses. Note that 150 // |rel_timeout_ms| can be Infinity. 151 // If woken, return "ok", otherwise return "timed-out". The initial check and 152 // the decision to wait happen atomically. 153 static Object WaitJs32(Isolate* isolate, WaitMode mode, 154 Handle<JSArrayBuffer> array_buffer, size_t addr, 155 int32_t value, double rel_timeout_ms); 156 157 // An version of WaitJs32 for int64_t values. 158 static Object WaitJs64(Isolate* isolate, WaitMode mode, 159 Handle<JSArrayBuffer> array_buffer, size_t addr, 160 int64_t value, double rel_timeout_ms); 161 162 // Same as WaitJs above except it returns 0 (ok), 1 (not equal) and 2 (timed 163 // out) as expected by Wasm. 164 V8_EXPORT_PRIVATE static Object WaitWasm32(Isolate* isolate, 165 Handle<JSArrayBuffer> array_buffer, 166 size_t addr, int32_t value, 167 int64_t rel_timeout_ns); 168 169 // Same as Wait32 above except it checks for an int64_t value in the 170 // array_buffer. 171 V8_EXPORT_PRIVATE static Object WaitWasm64(Isolate* isolate, 172 Handle<JSArrayBuffer> array_buffer, 173 size_t addr, int64_t value, 174 int64_t rel_timeout_ns); 175 176 // Wake |num_waiters_to_wake| threads that are waiting on the given |addr|. 177 // |num_waiters_to_wake| can be kWakeAll, in which case all waiters are 178 // woken. The rest of the waiters will continue to wait. The return value is 179 // the number of woken waiters. 180 V8_EXPORT_PRIVATE static Object Wake(Handle<JSArrayBuffer> array_buffer, 181 size_t addr, 182 uint32_t num_waiters_to_wake); 183 184 // Called before |isolate| dies. Removes async waiters owned by |isolate|. 185 static void IsolateDeinit(Isolate* isolate); 186 187 // Return the number of threads or async waiters waiting on |addr|. Should 188 // only be used for testing. 189 static Object NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer, 190 size_t addr); 191 192 // Return the number of async waiters (which belong to |isolate|) waiting. 193 // Should only be used for testing. 194 static Object NumAsyncWaitersForTesting(Isolate* isolate); 195 196 // Return the number of async waiters which were waiting for |addr| and are 197 // now waiting for the Promises to be resolved. Should only be used for 198 // testing. 199 static Object NumUnresolvedAsyncPromisesForTesting( 200 Handle<JSArrayBuffer> array_buffer, size_t addr); 201 202 private: 203 friend class FutexWaitListNode; 204 friend class AtomicsWaitWakeHandle; 205 friend class ResolveAsyncWaiterPromisesTask; 206 friend class AsyncWaiterTimeoutTask; 207 208 template <typename T> 209 static Object Wait(Isolate* isolate, WaitMode mode, 210 Handle<JSArrayBuffer> array_buffer, size_t addr, T value, 211 double rel_timeout_ms); 212 213 template <typename T> 214 static Object Wait(Isolate* isolate, WaitMode mode, 215 Handle<JSArrayBuffer> array_buffer, size_t addr, T value, 216 bool use_timeout, int64_t rel_timeout_ns, 217 CallType call_type = CallType::kIsNotWasm); 218 219 template <typename T> 220 static Object WaitSync(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, 221 size_t addr, T value, bool use_timeout, 222 int64_t rel_timeout_ns, CallType call_type); 223 224 template <typename T> 225 static Object WaitAsync(Isolate* isolate, Handle<JSArrayBuffer> array_buffer, 226 size_t addr, T value, bool use_timeout, 227 int64_t rel_timeout_ns, CallType call_type); 228 229 // Resolve the Promises of the async waiters which belong to |isolate|. 230 static void ResolveAsyncWaiterPromises(Isolate* isolate); 231 232 static void ResolveAsyncWaiterPromise(FutexWaitListNode* node); 233 234 static void HandleAsyncWaiterTimeout(FutexWaitListNode* node); 235 236 static void NotifyAsyncWaiter(FutexWaitListNode* node); 237 238 // Remove the node's Promise from the NativeContext's Promise set. 239 static void CleanupAsyncWaiterPromise(FutexWaitListNode* node); 240 }; 241 } // namespace internal 242 } // namespace v8 243 244 #endif // V8_EXECUTION_FUTEX_EMULATION_H_ 245