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