• 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 #include "src/execution/futex-emulation.h"
6 
7 #include <limits>
8 
9 #include "src/api/api-inl.h"
10 #include "src/base/logging.h"
11 #include "src/base/macros.h"
12 #include "src/execution/isolate.h"
13 #include "src/execution/vm-state-inl.h"
14 #include "src/handles/handles-inl.h"
15 #include "src/numbers/conversions.h"
16 #include "src/objects/bigint.h"
17 #include "src/objects/js-array-buffer-inl.h"
18 #include "src/objects/js-promise-inl.h"
19 #include "src/objects/objects-inl.h"
20 #include "src/tasks/cancelable-task.h"
21 
22 namespace v8 {
23 namespace internal {
24 
25 using AtomicsWaitEvent = v8::Isolate::AtomicsWaitEvent;
26 
27 class FutexWaitList {
28  public:
29   FutexWaitList() = default;
30   FutexWaitList(const FutexWaitList&) = delete;
31   FutexWaitList& operator=(const FutexWaitList&) = delete;
32 
33   void AddNode(FutexWaitListNode* node);
34   void RemoveNode(FutexWaitListNode* node);
35 
ToWaitLocation(const BackingStore * backing_store,size_t addr)36   static int8_t* ToWaitLocation(const BackingStore* backing_store,
37                                 size_t addr) {
38     return static_cast<int8_t*>(backing_store->buffer_start()) + addr;
39   }
40 
41   // Deletes "node" and returns the next node of its list.
DeleteAsyncWaiterNode(FutexWaitListNode * node)42   static FutexWaitListNode* DeleteAsyncWaiterNode(FutexWaitListNode* node) {
43     DCHECK_NOT_NULL(node->isolate_for_async_waiters_);
44     auto next = node->next_;
45     if (node->prev_ != nullptr) {
46       node->prev_->next_ = next;
47     }
48     if (next != nullptr) {
49       next->prev_ = node->prev_;
50     }
51     delete node;
52     return next;
53   }
54 
DeleteNodesForIsolate(Isolate * isolate,FutexWaitListNode ** head,FutexWaitListNode ** tail)55   static void DeleteNodesForIsolate(Isolate* isolate, FutexWaitListNode** head,
56                                     FutexWaitListNode** tail) {
57     // For updating head & tail once we've iterated all nodes.
58     FutexWaitListNode* new_head = nullptr;
59     FutexWaitListNode* new_tail = nullptr;
60     auto node = *head;
61     while (node != nullptr) {
62       if (node->isolate_for_async_waiters_ == isolate) {
63         node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
64         node = DeleteAsyncWaiterNode(node);
65       } else {
66         if (new_head == nullptr) {
67           new_head = node;
68         }
69         new_tail = node;
70         node = node->next_;
71       }
72     }
73     *head = new_head;
74     *tail = new_tail;
75   }
76 
77   // For checking the internal consistency of the FutexWaitList.
78   void Verify();
79   // Verifies the local consistency of |node|. If it's the first node of its
80   // list, it must be |head|, and if it's the last node, it must be |tail|.
81   void VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
82                   FutexWaitListNode* tail);
83   // Returns true if |node| is on the linked list starting with |head|.
84   static bool NodeIsOnList(FutexWaitListNode* node, FutexWaitListNode* head);
85 
86  private:
87   friend class FutexEmulation;
88 
89   struct HeadAndTail {
90     FutexWaitListNode* head;
91     FutexWaitListNode* tail;
92   };
93   // Location inside a shared buffer -> linked list of Nodes waiting on that
94   // location.
95   std::map<int8_t*, HeadAndTail> location_lists_;
96 
97   // Isolate* -> linked list of Nodes which are waiting for their Promises to
98   // be resolved.
99   std::map<Isolate*, HeadAndTail> isolate_promises_to_resolve_;
100 };
101 
102 namespace {
103 // `g_mutex` protects the composition of `g_wait_list` (i.e. no elements may be
104 // added or removed without holding this mutex), as well as the `waiting_`
105 // and `interrupted_` fields for each individual list node that is currently
106 // part of the list. It must be the mutex used together with the `cond_`
107 // condition variable of such nodes.
108 base::LazyMutex g_mutex = LAZY_MUTEX_INITIALIZER;
109 base::LazyInstance<FutexWaitList>::type g_wait_list = LAZY_INSTANCE_INITIALIZER;
110 }  // namespace
111 
~FutexWaitListNode()112 FutexWaitListNode::~FutexWaitListNode() {
113   // Assert that the timeout task was cancelled.
114   DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, timeout_task_id_);
115 }
116 
CancelTimeoutTask()117 bool FutexWaitListNode::CancelTimeoutTask() {
118   if (timeout_task_id_ != CancelableTaskManager::kInvalidTaskId) {
119     auto return_value = cancelable_task_manager_->TryAbort(timeout_task_id_);
120     timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
121     return return_value != TryAbortResult::kTaskRunning;
122   }
123   return true;
124 }
125 
NotifyWake()126 void FutexWaitListNode::NotifyWake() {
127   DCHECK(!IsAsync());
128   // Lock the FutexEmulation mutex before notifying. We know that the mutex
129   // will have been unlocked if we are currently waiting on the condition
130   // variable. The mutex will not be locked if FutexEmulation::Wait hasn't
131   // locked it yet. In that case, we set the interrupted_
132   // flag to true, which will be tested after the mutex locked by a future wait.
133   NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
134 
135   // if not waiting, this will not have any effect.
136   cond_.NotifyOne();
137   interrupted_ = true;
138 }
139 
140 class ResolveAsyncWaiterPromisesTask : public CancelableTask {
141  public:
ResolveAsyncWaiterPromisesTask(CancelableTaskManager * cancelable_task_manager,Isolate * isolate)142   ResolveAsyncWaiterPromisesTask(CancelableTaskManager* cancelable_task_manager,
143                                  Isolate* isolate)
144       : CancelableTask(cancelable_task_manager), isolate_(isolate) {}
145 
RunInternal()146   void RunInternal() override {
147     FutexEmulation::ResolveAsyncWaiterPromises(isolate_);
148   }
149 
150  private:
151   Isolate* isolate_;
152 };
153 
154 class AsyncWaiterTimeoutTask : public CancelableTask {
155  public:
AsyncWaiterTimeoutTask(CancelableTaskManager * cancelable_task_manager,FutexWaitListNode * node)156   AsyncWaiterTimeoutTask(CancelableTaskManager* cancelable_task_manager,
157                          FutexWaitListNode* node)
158       : CancelableTask(cancelable_task_manager), node_(node) {}
159 
RunInternal()160   void RunInternal() override {
161     FutexEmulation::HandleAsyncWaiterTimeout(node_);
162   }
163 
164  private:
165   FutexWaitListNode* node_;
166 };
167 
NotifyAsyncWaiter(FutexWaitListNode * node)168 void FutexEmulation::NotifyAsyncWaiter(FutexWaitListNode* node) {
169   // This function can run in any thread.
170 
171   g_mutex.Pointer()->AssertHeld();
172 
173   // Nullify the timeout time; this distinguishes timed out waiters from
174   // woken up ones.
175   node->async_timeout_time_ = base::TimeTicks();
176 
177   g_wait_list.Pointer()->RemoveNode(node);
178 
179   // Schedule a task for resolving the Promise. It's still possible that the
180   // timeout task runs before the promise resolving task. In that case, the
181   // timeout task will just ignore the node.
182   auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
183   auto it = isolate_map.find(node->isolate_for_async_waiters_);
184   if (it == isolate_map.end()) {
185     // This Isolate doesn't have other Promises to resolve at the moment.
186     isolate_map.insert(std::make_pair(node->isolate_for_async_waiters_,
187                                       FutexWaitList::HeadAndTail{node, node}));
188     auto task = std::make_unique<ResolveAsyncWaiterPromisesTask>(
189         node->cancelable_task_manager_, node->isolate_for_async_waiters_);
190     node->task_runner_->PostNonNestableTask(std::move(task));
191   } else {
192     // Add this Node into the existing list.
193     node->prev_ = it->second.tail;
194     it->second.tail->next_ = node;
195     it->second.tail = node;
196   }
197 }
198 
AddNode(FutexWaitListNode * node)199 void FutexWaitList::AddNode(FutexWaitListNode* node) {
200   DCHECK_NULL(node->prev_);
201   DCHECK_NULL(node->next_);
202   auto it = location_lists_.find(node->wait_location_);
203   if (it == location_lists_.end()) {
204     location_lists_.insert(
205         std::make_pair(node->wait_location_, HeadAndTail{node, node}));
206   } else {
207     it->second.tail->next_ = node;
208     node->prev_ = it->second.tail;
209     it->second.tail = node;
210   }
211 
212   Verify();
213 }
214 
RemoveNode(FutexWaitListNode * node)215 void FutexWaitList::RemoveNode(FutexWaitListNode* node) {
216   auto it = location_lists_.find(node->wait_location_);
217   DCHECK_NE(location_lists_.end(), it);
218   DCHECK(NodeIsOnList(node, it->second.head));
219 
220   if (node->prev_) {
221     node->prev_->next_ = node->next_;
222   } else {
223     DCHECK_EQ(node, it->second.head);
224     it->second.head = node->next_;
225   }
226 
227   if (node->next_) {
228     node->next_->prev_ = node->prev_;
229   } else {
230     DCHECK_EQ(node, it->second.tail);
231     it->second.tail = node->prev_;
232   }
233 
234   // If the node was the last one on its list, delete the whole list.
235   if (node->prev_ == nullptr && node->next_ == nullptr) {
236     location_lists_.erase(it);
237   }
238 
239   node->prev_ = node->next_ = nullptr;
240 
241   Verify();
242 }
243 
Wake()244 void AtomicsWaitWakeHandle::Wake() {
245   // Adding a separate `NotifyWake()` variant that doesn't acquire the lock
246   // itself would likely just add unnecessary complexity..
247   // The split lock by itself isn’t an issue, as long as the caller properly
248   // synchronizes this with the closing `AtomicsWaitCallback`.
249   {
250     NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
251     stopped_ = true;
252   }
253   isolate_->futex_wait_list_node()->NotifyWake();
254 }
255 
256 enum WaitReturnValue : int { kOk = 0, kNotEqual = 1, kTimedOut = 2 };
257 
258 namespace {
259 
WaitJsTranslateReturn(Isolate * isolate,Object res)260 Object WaitJsTranslateReturn(Isolate* isolate, Object res) {
261   if (res.IsSmi()) {
262     int val = Smi::ToInt(res);
263     switch (val) {
264       case WaitReturnValue::kOk:
265         return ReadOnlyRoots(isolate).ok_string();
266       case WaitReturnValue::kNotEqual:
267         return ReadOnlyRoots(isolate).not_equal_string();
268       case WaitReturnValue::kTimedOut:
269         return ReadOnlyRoots(isolate).timed_out_string();
270       default:
271         UNREACHABLE();
272     }
273   }
274   return res;
275 }
276 
277 }  // namespace
278 
WaitJs32(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,int32_t value,double rel_timeout_ms)279 Object FutexEmulation::WaitJs32(Isolate* isolate, WaitMode mode,
280                                 Handle<JSArrayBuffer> array_buffer, size_t addr,
281                                 int32_t value, double rel_timeout_ms) {
282   Object res =
283       Wait<int32_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
284   return WaitJsTranslateReturn(isolate, res);
285 }
286 
WaitJs64(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,int64_t value,double rel_timeout_ms)287 Object FutexEmulation::WaitJs64(Isolate* isolate, WaitMode mode,
288                                 Handle<JSArrayBuffer> array_buffer, size_t addr,
289                                 int64_t value, double rel_timeout_ms) {
290   Object res =
291       Wait<int64_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
292   return WaitJsTranslateReturn(isolate, res);
293 }
294 
WaitWasm32(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,int32_t value,int64_t rel_timeout_ns)295 Object FutexEmulation::WaitWasm32(Isolate* isolate,
296                                   Handle<JSArrayBuffer> array_buffer,
297                                   size_t addr, int32_t value,
298                                   int64_t rel_timeout_ns) {
299   return Wait<int32_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
300                        rel_timeout_ns >= 0, rel_timeout_ns, CallType::kIsWasm);
301 }
302 
WaitWasm64(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,int64_t value,int64_t rel_timeout_ns)303 Object FutexEmulation::WaitWasm64(Isolate* isolate,
304                                   Handle<JSArrayBuffer> array_buffer,
305                                   size_t addr, int64_t value,
306                                   int64_t rel_timeout_ns) {
307   return Wait<int64_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
308                        rel_timeout_ns >= 0, rel_timeout_ns, CallType::kIsWasm);
309 }
310 
311 template <typename T>
Wait(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,double rel_timeout_ms)312 Object FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
313                             Handle<JSArrayBuffer> array_buffer, size_t addr,
314                             T value, double rel_timeout_ms) {
315   DCHECK_LT(addr, array_buffer->byte_length());
316 
317   bool use_timeout = rel_timeout_ms != V8_INFINITY;
318   int64_t rel_timeout_ns = -1;
319 
320   if (use_timeout) {
321     // Convert to nanoseconds.
322     double timeout_ns = rel_timeout_ms *
323                         base::Time::kNanosecondsPerMicrosecond *
324                         base::Time::kMicrosecondsPerMillisecond;
325     if (timeout_ns > static_cast<double>(std::numeric_limits<int64_t>::max())) {
326       // 2**63 nanoseconds is 292 years. Let's just treat anything greater as
327       // infinite.
328       use_timeout = false;
329     } else {
330       rel_timeout_ns = static_cast<int64_t>(timeout_ns);
331     }
332   }
333   return Wait(isolate, mode, array_buffer, addr, value, use_timeout,
334               rel_timeout_ns);
335 }
336 
337 namespace {
WaitTimeoutInMs(double timeout_ns)338 double WaitTimeoutInMs(double timeout_ns) {
339   return timeout_ns < 0
340              ? V8_INFINITY
341              : timeout_ns / (base::Time::kNanosecondsPerMicrosecond *
342                              base::Time::kMicrosecondsPerMillisecond);
343 }
344 }  // namespace
345 
346 template <typename T>
Wait(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns,CallType call_type)347 Object FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
348                             Handle<JSArrayBuffer> array_buffer, size_t addr,
349                             T value, bool use_timeout, int64_t rel_timeout_ns,
350                             CallType call_type) {
351   if (mode == WaitMode::kSync) {
352     return WaitSync(isolate, array_buffer, addr, value, use_timeout,
353                     rel_timeout_ns, call_type);
354   }
355   DCHECK_EQ(mode, WaitMode::kAsync);
356   return WaitAsync(isolate, array_buffer, addr, value, use_timeout,
357                    rel_timeout_ns, call_type);
358 }
359 
360 template <typename T>
WaitSync(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns,CallType call_type)361 Object FutexEmulation::WaitSync(Isolate* isolate,
362                                 Handle<JSArrayBuffer> array_buffer, size_t addr,
363                                 T value, bool use_timeout,
364                                 int64_t rel_timeout_ns, CallType call_type) {
365   VMState<ATOMICS_WAIT> state(isolate);
366   base::TimeDelta rel_timeout =
367       base::TimeDelta::FromNanoseconds(rel_timeout_ns);
368 
369   // We have to convert the timeout back to double for the AtomicsWaitCallback.
370   double rel_timeout_ms = WaitTimeoutInMs(static_cast<double>(rel_timeout_ns));
371   AtomicsWaitWakeHandle stop_handle(isolate);
372 
373   isolate->RunAtomicsWaitCallback(AtomicsWaitEvent::kStartWait, array_buffer,
374                                   addr, value, rel_timeout_ms, &stop_handle);
375 
376   if (isolate->has_scheduled_exception()) {
377     return isolate->PromoteScheduledException();
378   }
379 
380   Handle<Object> result;
381   AtomicsWaitEvent callback_result = AtomicsWaitEvent::kWokenUp;
382 
383   do {  // Not really a loop, just makes it easier to break out early.
384     NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
385 
386     std::shared_ptr<BackingStore> backing_store =
387         array_buffer->GetBackingStore();
388     DCHECK(backing_store);
389     FutexWaitListNode* node = isolate->futex_wait_list_node();
390     node->backing_store_ = backing_store;
391     node->wait_addr_ = addr;
392     auto wait_location =
393         FutexWaitList::ToWaitLocation(backing_store.get(), addr);
394     node->wait_location_ = wait_location;
395     node->waiting_ = true;
396 
397     // Reset node->waiting_ = false when leaving this scope (but while
398     // still holding the lock).
399     FutexWaitListNode::ResetWaitingOnScopeExit reset_waiting(node);
400 
401     std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(wait_location);
402     T loaded_value = p->load();
403 #if defined(V8_TARGET_BIG_ENDIAN)
404     // If loading a Wasm value, it needs to be reversed on Big Endian platforms.
405     if (call_type == CallType::kIsWasm) {
406       DCHECK(sizeof(T) == kInt32Size || sizeof(T) == kInt64Size);
407       loaded_value = ByteReverse(loaded_value);
408     }
409 #endif
410     if (loaded_value != value) {
411       result = handle(Smi::FromInt(WaitReturnValue::kNotEqual), isolate);
412       callback_result = AtomicsWaitEvent::kNotEqual;
413       break;
414     }
415 
416     base::TimeTicks timeout_time;
417     base::TimeTicks current_time;
418 
419     if (use_timeout) {
420       current_time = base::TimeTicks::Now();
421       timeout_time = current_time + rel_timeout;
422     }
423 
424     g_wait_list.Pointer()->AddNode(node);
425 
426     while (true) {
427       bool interrupted = node->interrupted_;
428       node->interrupted_ = false;
429 
430       // Unlock the mutex here to prevent deadlock from lock ordering between
431       // mutex and mutexes locked by HandleInterrupts.
432       lock_guard.Unlock();
433 
434       // Because the mutex is unlocked, we have to be careful about not dropping
435       // an interrupt. The notification can happen in three different places:
436       // 1) Before Wait is called: the notification will be dropped, but
437       //    interrupted_ will be set to 1. This will be checked below.
438       // 2) After interrupted has been checked here, but before mutex is
439       //    acquired: interrupted is checked again below, with mutex locked.
440       //    Because the wakeup signal also acquires mutex, we know it will not
441       //    be able to notify until mutex is released below, when waiting on
442       //    the condition variable.
443       // 3) After the mutex is released in the call to WaitFor(): this
444       // notification will wake up the condition variable. node->waiting() will
445       // be false, so we'll loop and then check interrupts.
446       if (interrupted) {
447         Object interrupt_object = isolate->stack_guard()->HandleInterrupts();
448         if (interrupt_object.IsException(isolate)) {
449           result = handle(interrupt_object, isolate);
450           callback_result = AtomicsWaitEvent::kTerminatedExecution;
451           lock_guard.Lock();
452           break;
453         }
454       }
455 
456       lock_guard.Lock();
457 
458       if (node->interrupted_) {
459         // An interrupt occurred while the mutex was unlocked. Don't wait yet.
460         continue;
461       }
462 
463       if (stop_handle.has_stopped()) {
464         node->waiting_ = false;
465         callback_result = AtomicsWaitEvent::kAPIStopped;
466       }
467 
468       if (!node->waiting_) {
469         result = handle(Smi::FromInt(WaitReturnValue::kOk), isolate);
470         break;
471       }
472 
473       // No interrupts, now wait.
474       if (use_timeout) {
475         current_time = base::TimeTicks::Now();
476         if (current_time >= timeout_time) {
477           result = handle(Smi::FromInt(WaitReturnValue::kTimedOut), isolate);
478           callback_result = AtomicsWaitEvent::kTimedOut;
479           break;
480         }
481 
482         base::TimeDelta time_until_timeout = timeout_time - current_time;
483         DCHECK_GE(time_until_timeout.InMicroseconds(), 0);
484         bool wait_for_result =
485             node->cond_.WaitFor(g_mutex.Pointer(), time_until_timeout);
486         USE(wait_for_result);
487       } else {
488         node->cond_.Wait(g_mutex.Pointer());
489       }
490 
491       // Spurious wakeup, interrupt or timeout.
492     }
493 
494     g_wait_list.Pointer()->RemoveNode(node);
495   } while (false);
496 
497   isolate->RunAtomicsWaitCallback(callback_result, array_buffer, addr, value,
498                                   rel_timeout_ms, nullptr);
499 
500   if (isolate->has_scheduled_exception()) {
501     CHECK_NE(callback_result, AtomicsWaitEvent::kTerminatedExecution);
502     result = handle(isolate->PromoteScheduledException(), isolate);
503   }
504 
505   return *result;
506 }
507 
FutexWaitListNode(const std::shared_ptr<BackingStore> & backing_store,size_t wait_addr,Handle<JSObject> promise,Isolate * isolate)508 FutexWaitListNode::FutexWaitListNode(
509     const std::shared_ptr<BackingStore>& backing_store, size_t wait_addr,
510     Handle<JSObject> promise, Isolate* isolate)
511     : isolate_for_async_waiters_(isolate),
512       backing_store_(backing_store),
513       wait_addr_(wait_addr),
514       wait_location_(
515           FutexWaitList::ToWaitLocation(backing_store.get(), wait_addr)),
516       waiting_(true) {
517   auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
518   task_runner_ = V8::GetCurrentPlatform()->GetForegroundTaskRunner(v8_isolate);
519   cancelable_task_manager_ = isolate->cancelable_task_manager();
520 
521   v8::Local<v8::Promise> local_promise = Utils::PromiseToLocal(promise);
522   promise_.Reset(v8_isolate, local_promise);
523   promise_.SetWeak();
524   Handle<NativeContext> native_context(isolate->native_context());
525   v8::Local<v8::Context> local_native_context =
526       Utils::ToLocal(Handle<Context>::cast(native_context));
527   native_context_.Reset(v8_isolate, local_native_context);
528   native_context_.SetWeak();
529 }
530 
531 template <typename T>
WaitAsync(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns,CallType call_type)532 Object FutexEmulation::WaitAsync(Isolate* isolate,
533                                  Handle<JSArrayBuffer> array_buffer,
534                                  size_t addr, T value, bool use_timeout,
535                                  int64_t rel_timeout_ns, CallType call_type) {
536   base::TimeDelta rel_timeout =
537       base::TimeDelta::FromNanoseconds(rel_timeout_ns);
538 
539   Factory* factory = isolate->factory();
540   Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
541   Handle<JSObject> promise_capability = factory->NewJSPromise();
542 
543   enum class ResultKind { kNotEqual, kTimedOut, kAsync };
544   ResultKind result_kind;
545   {
546     // 16. Perform EnterCriticalSection(WL).
547     NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
548 
549     std::shared_ptr<BackingStore> backing_store =
550         array_buffer->GetBackingStore();
551 
552     // 17. Let w be ! AtomicLoad(typedArray, i).
553     std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(
554         static_cast<int8_t*>(backing_store->buffer_start()) + addr);
555     T loaded_value = p->load();
556 #if defined(V8_TARGET_BIG_ENDIAN)
557     // If loading a Wasm value, it needs to be reversed on Big Endian platforms.
558     if (call_type == CallType::kIsWasm) {
559       DCHECK(sizeof(T) == kInt32Size || sizeof(T) == kInt64Size);
560       loaded_value = ByteReverse(loaded_value);
561     }
562 #endif
563     if (loaded_value != value) {
564       result_kind = ResultKind::kNotEqual;
565     } else if (use_timeout && rel_timeout_ns == 0) {
566       result_kind = ResultKind::kTimedOut;
567     } else {
568       result_kind = ResultKind::kAsync;
569 
570       FutexWaitListNode* node = new FutexWaitListNode(
571           backing_store, addr, promise_capability, isolate);
572 
573       if (use_timeout) {
574         node->async_timeout_time_ = base::TimeTicks::Now() + rel_timeout;
575         auto task = std::make_unique<AsyncWaiterTimeoutTask>(
576             node->cancelable_task_manager_, node);
577         node->timeout_task_id_ = task->id();
578         node->task_runner_->PostNonNestableDelayedTask(
579             std::move(task), rel_timeout.InSecondsF());
580       }
581 
582       g_wait_list.Pointer()->AddNode(node);
583     }
584 
585     // Leaving the block collapses the following steps:
586     // 18.a. Perform LeaveCriticalSection(WL).
587     // 19.b. Perform LeaveCriticalSection(WL).
588     // 24. Perform LeaveCriticalSection(WL).
589   }
590 
591   switch (result_kind) {
592     case ResultKind::kNotEqual:
593       // 18. If v is not equal to w, then
594       //   ...
595       //   c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
596       //   d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
597       //     "not-equal").
598       //   e. Return resultObject.
599       CHECK(JSReceiver::CreateDataProperty(
600                 isolate, result, factory->async_string(),
601                 factory->false_value(), Just(kDontThrow))
602                 .FromJust());
603       CHECK(JSReceiver::CreateDataProperty(
604                 isolate, result, factory->value_string(),
605                 factory->not_equal_string(), Just(kDontThrow))
606                 .FromJust());
607       break;
608 
609     case ResultKind::kTimedOut:
610       // 19. If t is 0 and mode is async, then
611       //   ...
612       //   c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
613       //   d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
614       //     "timed-out").
615       //   e. Return resultObject.
616       CHECK(JSReceiver::CreateDataProperty(
617                 isolate, result, factory->async_string(),
618                 factory->false_value(), Just(kDontThrow))
619                 .FromJust());
620       CHECK(JSReceiver::CreateDataProperty(
621                 isolate, result, factory->value_string(),
622                 factory->timed_out_string(), Just(kDontThrow))
623                 .FromJust());
624       break;
625 
626     case ResultKind::kAsync:
627       // Add the Promise into the NativeContext's atomics_waitasync_promises
628       // set, so that the list keeps it alive.
629       Handle<NativeContext> native_context(isolate->native_context());
630       Handle<OrderedHashSet> promises(
631           native_context->atomics_waitasync_promises(), isolate);
632       promises = OrderedHashSet::Add(isolate, promises, promise_capability)
633                      .ToHandleChecked();
634       native_context->set_atomics_waitasync_promises(*promises);
635 
636       // 26. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
637       // 27. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
638       // promiseCapability.[[Promise]]).
639       // 28. Return resultObject.
640       CHECK(JSReceiver::CreateDataProperty(
641                 isolate, result, factory->async_string(), factory->true_value(),
642                 Just(kDontThrow))
643                 .FromJust());
644       CHECK(JSReceiver::CreateDataProperty(isolate, result,
645                                            factory->value_string(),
646                                            promise_capability, Just(kDontThrow))
647                 .FromJust());
648       break;
649   }
650 
651   return *result;
652 }
653 
Wake(Handle<JSArrayBuffer> array_buffer,size_t addr,uint32_t num_waiters_to_wake)654 Object FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
655                             uint32_t num_waiters_to_wake) {
656   DCHECK_LT(addr, array_buffer->byte_length());
657 
658   int waiters_woken = 0;
659   std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
660   auto wait_location = FutexWaitList::ToWaitLocation(backing_store.get(), addr);
661 
662   NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
663 
664   auto& location_lists = g_wait_list.Pointer()->location_lists_;
665   auto it = location_lists.find(wait_location);
666   if (it == location_lists.end()) {
667     return Smi::zero();
668   }
669   FutexWaitListNode* node = it->second.head;
670   while (node && num_waiters_to_wake > 0) {
671     bool delete_this_node = false;
672     std::shared_ptr<BackingStore> node_backing_store =
673         node->backing_store_.lock();
674 
675     if (!node->waiting_) {
676       node = node->next_;
677       continue;
678     }
679     // Relying on wait_location_ here is not enough, since we need to guard
680     // against the case where the BackingStore of the node has been deleted and
681     // a new BackingStore recreated in the same memory area.
682     if (backing_store.get() == node_backing_store.get()) {
683       DCHECK_EQ(addr, node->wait_addr_);
684       node->waiting_ = false;
685 
686       // Retrieve the next node to iterate before calling NotifyAsyncWaiter,
687       // since NotifyAsyncWaiter will take the node out of the linked list.
688       auto old_node = node;
689       node = node->next_;
690       if (old_node->IsAsync()) {
691         NotifyAsyncWaiter(old_node);
692       } else {
693         // WaitSync will remove the node from the list.
694         old_node->cond_.NotifyOne();
695       }
696       if (num_waiters_to_wake != kWakeAll) {
697         --num_waiters_to_wake;
698       }
699       waiters_woken++;
700       continue;
701     }
702     DCHECK_EQ(nullptr, node_backing_store.get());
703     if (node->async_timeout_time_ == base::TimeTicks()) {
704       // Backing store has been deleted and the node is still waiting, and
705       // there's no timeout. It's never going to be woken up, so we can clean
706       // it up now. We don't need to cancel the timeout task, because there is
707       // none.
708 
709       // This cleanup code is not very efficient, since it only kicks in when
710       // a new BackingStore has been created in the same memory area where the
711       // deleted BackingStore was.
712       DCHECK(node->IsAsync());
713       DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
714       delete_this_node = true;
715     }
716     if (node->IsAsync() && node->native_context_.IsEmpty()) {
717       // The NativeContext related to the async waiter has been deleted.
718       // Ditto, clean up now.
719 
720       // Using the CancelableTaskManager here is OK since the Isolate is
721       // guaranteed to be alive - FutexEmulation::IsolateDeinit removes all
722       // FutexWaitListNodes owned by an Isolate which is going to die.
723       if (node->CancelTimeoutTask()) {
724         delete_this_node = true;
725       }
726       // If cancelling the timeout task failed, the timeout task is already
727       // running and will clean up the node.
728     }
729 
730     if (delete_this_node) {
731       auto old_node = node;
732       node = node->next_;
733       g_wait_list.Pointer()->RemoveNode(old_node);
734       DCHECK_EQ(CancelableTaskManager::kInvalidTaskId,
735                 old_node->timeout_task_id_);
736       delete old_node;
737     } else {
738       node = node->next_;
739     }
740   }
741 
742   return Smi::FromInt(waiters_woken);
743 }
744 
CleanupAsyncWaiterPromise(FutexWaitListNode * node)745 void FutexEmulation::CleanupAsyncWaiterPromise(FutexWaitListNode* node) {
746   // This function must run in the main thread of node's Isolate. This function
747   // may allocate memory. To avoid deadlocks, we shouldn't be holding g_mutex.
748 
749   DCHECK(node->IsAsync());
750 
751   Isolate* isolate = node->isolate_for_async_waiters_;
752   auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
753 
754   if (!node->promise_.IsEmpty()) {
755     Handle<JSPromise> promise = Handle<JSPromise>::cast(
756         Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
757     // Promise keeps the NativeContext alive.
758     DCHECK(!node->native_context_.IsEmpty());
759     Handle<NativeContext> native_context = Handle<NativeContext>::cast(
760         Utils::OpenHandle(*node->native_context_.Get(v8_isolate)));
761 
762     // Remove the Promise from the NativeContext's set.
763     Handle<OrderedHashSet> promises(
764         native_context->atomics_waitasync_promises(), isolate);
765     bool was_deleted = OrderedHashSet::Delete(isolate, *promises, *promise);
766     DCHECK(was_deleted);
767     USE(was_deleted);
768     promises = OrderedHashSet::Shrink(isolate, promises);
769     native_context->set_atomics_waitasync_promises(*promises);
770   } else {
771     // NativeContext keeps the Promise alive; if the Promise is dead then
772     // surely NativeContext is too.
773     DCHECK(node->native_context_.IsEmpty());
774   }
775 }
776 
ResolveAsyncWaiterPromise(FutexWaitListNode * node)777 void FutexEmulation::ResolveAsyncWaiterPromise(FutexWaitListNode* node) {
778   // This function must run in the main thread of node's Isolate.
779 
780   auto v8_isolate =
781       reinterpret_cast<v8::Isolate*>(node->isolate_for_async_waiters_);
782 
783   // Try to cancel the timeout task (if one exists). If the timeout task exists,
784   // cancelling it will always succeed. It's not possible for the timeout task
785   // to be running, since it's scheduled to run in the same thread as this task.
786 
787   // Using the CancelableTaskManager here is OK since the Isolate is guaranteed
788   // to be alive - FutexEmulation::IsolateDeinit removes all FutexWaitListNodes
789   // owned by an Isolate which is going to die.
790   bool success = node->CancelTimeoutTask();
791   DCHECK(success);
792   USE(success);
793 
794   if (!node->promise_.IsEmpty()) {
795     DCHECK(!node->native_context_.IsEmpty());
796     Local<v8::Context> native_context = node->native_context_.Get(v8_isolate);
797     v8::Context::Scope contextScope(native_context);
798     Handle<JSPromise> promise = Handle<JSPromise>::cast(
799         Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
800     Handle<String> result_string;
801     // When waiters are notified, their async_timeout_time_ is reset. Having a
802     // non-zero async_timeout_time_ here means the waiter timed out.
803     if (node->async_timeout_time_ != base::TimeTicks()) {
804       DCHECK(node->waiting_);
805       result_string =
806           node->isolate_for_async_waiters_->factory()->timed_out_string();
807     } else {
808       DCHECK(!node->waiting_);
809       result_string = node->isolate_for_async_waiters_->factory()->ok_string();
810     }
811     MaybeHandle<Object> resolve_result =
812         JSPromise::Resolve(promise, result_string);
813     DCHECK(!resolve_result.is_null());
814     USE(resolve_result);
815   }
816 }
817 
ResolveAsyncWaiterPromises(Isolate * isolate)818 void FutexEmulation::ResolveAsyncWaiterPromises(Isolate* isolate) {
819   // This function must run in the main thread of isolate.
820 
821   FutexWaitListNode* node;
822   {
823     NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
824 
825     auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
826     auto it = isolate_map.find(isolate);
827     DCHECK_NE(isolate_map.end(), it);
828 
829     node = it->second.head;
830     isolate_map.erase(it);
831   }
832 
833   // The list of nodes starting from "node" are no longer on any list, so it's
834   // ok to iterate them without holding the mutex. We also need to not hold the
835   // mutex while calling CleanupAsyncWaiterPromise, since it may allocate
836   // memory.
837   HandleScope handle_scope(isolate);
838   while (node) {
839     DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
840     DCHECK(!node->waiting_);
841     ResolveAsyncWaiterPromise(node);
842     CleanupAsyncWaiterPromise(node);
843     // We've already tried to cancel the timeout task for the node; since we're
844     // now in the same thread the timeout task is supposed to run, we know the
845     // timeout task will never happen, and it's safe to delete the node here.
846     DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
847     node = FutexWaitList::DeleteAsyncWaiterNode(node);
848   }
849 }
850 
HandleAsyncWaiterTimeout(FutexWaitListNode * node)851 void FutexEmulation::HandleAsyncWaiterTimeout(FutexWaitListNode* node) {
852   // This function must run in the main thread of node's Isolate.
853   DCHECK(node->IsAsync());
854 
855   {
856     NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
857 
858     node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
859     if (!node->waiting_) {
860       // If the Node is not waiting, it's already scheduled to have its Promise
861       // resolved. Ignore the timeout.
862       return;
863     }
864     g_wait_list.Pointer()->RemoveNode(node);
865   }
866 
867   // "node" has been taken out of the lists, so it's ok to access it without
868   // holding the mutex. We also need to not hold the mutex while calling
869   // CleanupAsyncWaiterPromise, since it may allocate memory.
870   HandleScope handle_scope(node->isolate_for_async_waiters_);
871   ResolveAsyncWaiterPromise(node);
872   CleanupAsyncWaiterPromise(node);
873   delete node;
874 }
875 
IsolateDeinit(Isolate * isolate)876 void FutexEmulation::IsolateDeinit(Isolate* isolate) {
877   NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
878 
879   // Iterate all locations to find nodes belonging to "isolate" and delete them.
880   // The Isolate is going away; don't bother cleaning up the Promises in the
881   // NativeContext. Also we don't need to cancel the timeout tasks, since they
882   // will be cancelled by Isolate::Deinit.
883   {
884     auto& location_lists = g_wait_list.Pointer()->location_lists_;
885     auto it = location_lists.begin();
886     while (it != location_lists.end()) {
887       FutexWaitListNode*& head = it->second.head;
888       FutexWaitListNode*& tail = it->second.tail;
889       FutexWaitList::DeleteNodesForIsolate(isolate, &head, &tail);
890       // head and tail are either both nullptr or both non-nullptr.
891       DCHECK_EQ(head == nullptr, tail == nullptr);
892       if (head == nullptr) {
893         location_lists.erase(it++);
894       } else {
895         ++it;
896       }
897     }
898   }
899 
900   {
901     auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
902     auto it = isolate_map.find(isolate);
903     if (it != isolate_map.end()) {
904       auto node = it->second.head;
905       while (node) {
906         DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
907         node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
908         node = FutexWaitList::DeleteAsyncWaiterNode(node);
909       }
910       isolate_map.erase(it);
911     }
912   }
913 
914   g_wait_list.Pointer()->Verify();
915 }
916 
NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,size_t addr)917 Object FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
918                                             size_t addr) {
919   DCHECK_LT(addr, array_buffer->byte_length());
920   std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
921 
922   NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
923 
924   auto wait_location = FutexWaitList::ToWaitLocation(backing_store.get(), addr);
925   auto& location_lists = g_wait_list.Pointer()->location_lists_;
926   auto it = location_lists.find(wait_location);
927   if (it == location_lists.end()) {
928     return Smi::zero();
929   }
930   int waiters = 0;
931   FutexWaitListNode* node = it->second.head;
932   while (node != nullptr) {
933     std::shared_ptr<BackingStore> node_backing_store =
934         node->backing_store_.lock();
935     if (backing_store.get() == node_backing_store.get() && node->waiting_) {
936       waiters++;
937     }
938 
939     node = node->next_;
940   }
941 
942   return Smi::FromInt(waiters);
943 }
944 
NumAsyncWaitersForTesting(Isolate * isolate)945 Object FutexEmulation::NumAsyncWaitersForTesting(Isolate* isolate) {
946   NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
947 
948   int waiters = 0;
949   for (const auto& it : g_wait_list.Pointer()->location_lists_) {
950     FutexWaitListNode* node = it.second.head;
951     while (node != nullptr) {
952       if (node->isolate_for_async_waiters_ == isolate && node->waiting_) {
953         waiters++;
954       }
955       node = node->next_;
956     }
957   }
958 
959   return Smi::FromInt(waiters);
960 }
961 
NumUnresolvedAsyncPromisesForTesting(Handle<JSArrayBuffer> array_buffer,size_t addr)962 Object FutexEmulation::NumUnresolvedAsyncPromisesForTesting(
963     Handle<JSArrayBuffer> array_buffer, size_t addr) {
964   DCHECK_LT(addr, array_buffer->byte_length());
965   std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
966 
967   NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
968 
969   int waiters = 0;
970   auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
971   for (const auto& it : isolate_map) {
972     FutexWaitListNode* node = it.second.head;
973     while (node != nullptr) {
974       std::shared_ptr<BackingStore> node_backing_store =
975           node->backing_store_.lock();
976       if (backing_store.get() == node_backing_store.get() &&
977           addr == node->wait_addr_ && !node->waiting_) {
978         waiters++;
979       }
980 
981       node = node->next_;
982     }
983   }
984 
985   return Smi::FromInt(waiters);
986 }
987 
VerifyNode(FutexWaitListNode * node,FutexWaitListNode * head,FutexWaitListNode * tail)988 void FutexWaitList::VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
989                                FutexWaitListNode* tail) {
990 #ifdef DEBUG
991   if (node->next_ != nullptr) {
992     DCHECK_NE(node, tail);
993     DCHECK_EQ(node, node->next_->prev_);
994   } else {
995     DCHECK_EQ(node, tail);
996   }
997   if (node->prev_ != nullptr) {
998     DCHECK_NE(node, head);
999     DCHECK_EQ(node, node->prev_->next_);
1000   } else {
1001     DCHECK_EQ(node, head);
1002   }
1003 
1004   if (node->async_timeout_time_ != base::TimeTicks()) {
1005     DCHECK(node->IsAsync());
1006   }
1007 
1008   DCHECK(NodeIsOnList(node, head));
1009 #endif  // DEBUG
1010 }
1011 
Verify()1012 void FutexWaitList::Verify() {
1013 #ifdef DEBUG
1014   for (const auto& it : location_lists_) {
1015     FutexWaitListNode* node = it.second.head;
1016     while (node != nullptr) {
1017       VerifyNode(node, it.second.head, it.second.tail);
1018       node = node->next_;
1019     }
1020   }
1021 
1022   for (const auto& it : isolate_promises_to_resolve_) {
1023     auto node = it.second.head;
1024     while (node != nullptr) {
1025       VerifyNode(node, it.second.head, it.second.tail);
1026       DCHECK_EQ(it.first, node->isolate_for_async_waiters_);
1027       node = node->next_;
1028     }
1029   }
1030 #endif  // DEBUG
1031 }
1032 
NodeIsOnList(FutexWaitListNode * node,FutexWaitListNode * head)1033 bool FutexWaitList::NodeIsOnList(FutexWaitListNode* node,
1034                                  FutexWaitListNode* head) {
1035   auto n = head;
1036   while (n != nullptr) {
1037     if (n == node) {
1038       return true;
1039     }
1040     n = n->next_;
1041   }
1042   return false;
1043 }
1044 
1045 }  // namespace internal
1046 }  // namespace v8
1047