• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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