• 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 
31   void AddNode(FutexWaitListNode* node);
32   void RemoveNode(FutexWaitListNode* node);
33 
ToWaitLocation(const BackingStore * backing_store,size_t addr)34   static int8_t* ToWaitLocation(const BackingStore* backing_store,
35                                 size_t addr) {
36     return static_cast<int8_t*>(backing_store->buffer_start()) + addr;
37   }
38 
39   // Deletes "node" and returns the next node of its list.
DeleteAsyncWaiterNode(FutexWaitListNode * node)40   static FutexWaitListNode* DeleteAsyncWaiterNode(FutexWaitListNode* node) {
41     DCHECK_NOT_NULL(node->isolate_for_async_waiters_);
42     auto next = node->next_;
43     if (node->prev_ != nullptr) {
44       node->prev_->next_ = next;
45     }
46     if (next != nullptr) {
47       next->prev_ = node->prev_;
48     }
49     delete node;
50     return next;
51   }
52 
DeleteNodesForIsolate(Isolate * isolate,FutexWaitListNode ** head,FutexWaitListNode ** tail)53   static void DeleteNodesForIsolate(Isolate* isolate, FutexWaitListNode** head,
54                                     FutexWaitListNode** tail) {
55     // For updating head & tail once we've iterated all nodes.
56     FutexWaitListNode* new_head = nullptr;
57     FutexWaitListNode* new_tail = nullptr;
58     auto node = *head;
59     while (node != nullptr) {
60       if (node->isolate_for_async_waiters_ == isolate) {
61         node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
62         node = DeleteAsyncWaiterNode(node);
63       } else {
64         if (new_head == nullptr) {
65           new_head = node;
66         }
67         new_tail = node;
68         node = node->next_;
69       }
70     }
71     *head = new_head;
72     *tail = new_tail;
73   }
74 
75   // For checking the internal consistency of the FutexWaitList.
76   void Verify();
77   // Verifies the local consistency of |node|. If it's the first node of its
78   // list, it must be |head|, and if it's the last node, it must be |tail|.
79   void VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
80                   FutexWaitListNode* tail);
81   // Returns true if |node| is on the linked list starting with |head|.
82   static bool NodeIsOnList(FutexWaitListNode* node, FutexWaitListNode* head);
83 
84  private:
85   friend class FutexEmulation;
86 
87   struct HeadAndTail {
88     FutexWaitListNode* head;
89     FutexWaitListNode* tail;
90   };
91   // Location inside a shared buffer -> linked list of Nodes waiting on that
92   // location.
93   std::map<int8_t*, HeadAndTail> location_lists_;
94 
95   // Isolate* -> linked list of Nodes which are waiting for their Promises to
96   // be resolved.
97   std::map<Isolate*, HeadAndTail> isolate_promises_to_resolve_;
98 
99   DISALLOW_COPY_AND_ASSIGN(FutexWaitList);
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   NoHeapAllocationMutexGuard 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     NoHeapAllocationMutexGuard 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);
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);
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)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   if (mode == WaitMode::kSync) {
351     return WaitSync(isolate, array_buffer, addr, value, use_timeout,
352                     rel_timeout_ns);
353   }
354   DCHECK_EQ(mode, WaitMode::kAsync);
355   return WaitAsync(isolate, array_buffer, addr, value, use_timeout,
356                    rel_timeout_ns);
357 }
358 
359 template <typename T>
WaitSync(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns)360 Object FutexEmulation::WaitSync(Isolate* isolate,
361                                 Handle<JSArrayBuffer> array_buffer, size_t addr,
362                                 T value, bool use_timeout,
363                                 int64_t rel_timeout_ns) {
364   VMState<ATOMICS_WAIT> state(isolate);
365   base::TimeDelta rel_timeout =
366       base::TimeDelta::FromNanoseconds(rel_timeout_ns);
367 
368   // We have to convert the timeout back to double for the AtomicsWaitCallback.
369   double rel_timeout_ms = WaitTimeoutInMs(static_cast<double>(rel_timeout_ns));
370   AtomicsWaitWakeHandle stop_handle(isolate);
371 
372   isolate->RunAtomicsWaitCallback(AtomicsWaitEvent::kStartWait, array_buffer,
373                                   addr, value, rel_timeout_ms, &stop_handle);
374 
375   if (isolate->has_scheduled_exception()) {
376     return isolate->PromoteScheduledException();
377   }
378 
379   Handle<Object> result;
380   AtomicsWaitEvent callback_result = AtomicsWaitEvent::kWokenUp;
381 
382   do {  // Not really a loop, just makes it easier to break out early.
383     NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
384 
385     std::shared_ptr<BackingStore> backing_store =
386         array_buffer->GetBackingStore();
387     DCHECK(backing_store);
388     FutexWaitListNode* node = isolate->futex_wait_list_node();
389     node->backing_store_ = backing_store;
390     node->wait_addr_ = addr;
391     auto wait_location =
392         FutexWaitList::ToWaitLocation(backing_store.get(), addr);
393     node->wait_location_ = wait_location;
394     node->waiting_ = true;
395 
396     // Reset node->waiting_ = false when leaving this scope (but while
397     // still holding the lock).
398     FutexWaitListNode::ResetWaitingOnScopeExit reset_waiting(node);
399 
400     std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(wait_location);
401     if (p->load() != value) {
402       result = handle(Smi::FromInt(WaitReturnValue::kNotEqual), isolate);
403       callback_result = AtomicsWaitEvent::kNotEqual;
404       break;
405     }
406 
407     base::TimeTicks timeout_time;
408     base::TimeTicks current_time;
409 
410     if (use_timeout) {
411       current_time = base::TimeTicks::Now();
412       timeout_time = current_time + rel_timeout;
413     }
414 
415     g_wait_list.Pointer()->AddNode(node);
416 
417     while (true) {
418       bool interrupted = node->interrupted_;
419       node->interrupted_ = false;
420 
421       // Unlock the mutex here to prevent deadlock from lock ordering between
422       // mutex and mutexes locked by HandleInterrupts.
423       lock_guard.Unlock();
424 
425       // Because the mutex is unlocked, we have to be careful about not dropping
426       // an interrupt. The notification can happen in three different places:
427       // 1) Before Wait is called: the notification will be dropped, but
428       //    interrupted_ will be set to 1. This will be checked below.
429       // 2) After interrupted has been checked here, but before mutex is
430       //    acquired: interrupted is checked again below, with mutex locked.
431       //    Because the wakeup signal also acquires mutex, we know it will not
432       //    be able to notify until mutex is released below, when waiting on
433       //    the condition variable.
434       // 3) After the mutex is released in the call to WaitFor(): this
435       // notification will wake up the condition variable. node->waiting() will
436       // be false, so we'll loop and then check interrupts.
437       if (interrupted) {
438         Object interrupt_object = isolate->stack_guard()->HandleInterrupts();
439         if (interrupt_object.IsException(isolate)) {
440           result = handle(interrupt_object, isolate);
441           callback_result = AtomicsWaitEvent::kTerminatedExecution;
442           lock_guard.Lock();
443           break;
444         }
445       }
446 
447       lock_guard.Lock();
448 
449       if (node->interrupted_) {
450         // An interrupt occurred while the mutex was unlocked. Don't wait yet.
451         continue;
452       }
453 
454       if (stop_handle.has_stopped()) {
455         node->waiting_ = false;
456         callback_result = AtomicsWaitEvent::kAPIStopped;
457       }
458 
459       if (!node->waiting_) {
460         result = handle(Smi::FromInt(WaitReturnValue::kOk), isolate);
461         break;
462       }
463 
464       // No interrupts, now wait.
465       if (use_timeout) {
466         current_time = base::TimeTicks::Now();
467         if (current_time >= timeout_time) {
468           result = handle(Smi::FromInt(WaitReturnValue::kTimedOut), isolate);
469           callback_result = AtomicsWaitEvent::kTimedOut;
470           break;
471         }
472 
473         base::TimeDelta time_until_timeout = timeout_time - current_time;
474         DCHECK_GE(time_until_timeout.InMicroseconds(), 0);
475         bool wait_for_result =
476             node->cond_.WaitFor(g_mutex.Pointer(), time_until_timeout);
477         USE(wait_for_result);
478       } else {
479         node->cond_.Wait(g_mutex.Pointer());
480       }
481 
482       // Spurious wakeup, interrupt or timeout.
483     }
484 
485     g_wait_list.Pointer()->RemoveNode(node);
486   } while (false);
487 
488   isolate->RunAtomicsWaitCallback(callback_result, array_buffer, addr, value,
489                                   rel_timeout_ms, nullptr);
490 
491   if (isolate->has_scheduled_exception()) {
492     CHECK_NE(callback_result, AtomicsWaitEvent::kTerminatedExecution);
493     result = handle(isolate->PromoteScheduledException(), isolate);
494   }
495 
496   return *result;
497 }
498 
FutexWaitListNode(const std::shared_ptr<BackingStore> & backing_store,size_t wait_addr,Handle<JSObject> promise,Isolate * isolate)499 FutexWaitListNode::FutexWaitListNode(
500     const std::shared_ptr<BackingStore>& backing_store, size_t wait_addr,
501     Handle<JSObject> promise, Isolate* isolate)
502     : isolate_for_async_waiters_(isolate),
503       backing_store_(backing_store),
504       wait_addr_(wait_addr),
505       wait_location_(
506           FutexWaitList::ToWaitLocation(backing_store.get(), wait_addr)),
507       waiting_(true) {
508   auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
509   task_runner_ = V8::GetCurrentPlatform()->GetForegroundTaskRunner(v8_isolate);
510   cancelable_task_manager_ = isolate->cancelable_task_manager();
511 
512   v8::Local<v8::Promise> local_promise = Utils::PromiseToLocal(promise);
513   promise_.Reset(v8_isolate, local_promise);
514   promise_.SetWeak();
515   Handle<NativeContext> native_context(isolate->native_context());
516   v8::Local<v8::Context> local_native_context =
517       Utils::ToLocal(Handle<Context>::cast(native_context));
518   native_context_.Reset(v8_isolate, local_native_context);
519   native_context_.SetWeak();
520 
521   // Add the Promise into the NativeContext's atomics_waitasync_promises set, so
522   // that the list keeps it alive.
523   Handle<OrderedHashSet> promises(native_context->atomics_waitasync_promises(),
524                                   isolate);
525   promises = OrderedHashSet::Add(isolate, promises, promise).ToHandleChecked();
526   native_context->set_atomics_waitasync_promises(*promises);
527 }
528 
529 template <typename T>
WaitAsync(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns)530 Object FutexEmulation::WaitAsync(Isolate* isolate,
531                                  Handle<JSArrayBuffer> array_buffer,
532                                  size_t addr, T value, bool use_timeout,
533                                  int64_t rel_timeout_ns) {
534   DCHECK(FLAG_harmony_atomics_waitasync);
535   base::TimeDelta rel_timeout =
536       base::TimeDelta::FromNanoseconds(rel_timeout_ns);
537 
538   Factory* factory = isolate->factory();
539   Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
540 
541   std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
542 
543   // 17. Let w be ! AtomicLoad(typedArray, i).
544   std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(
545       static_cast<int8_t*>(backing_store->buffer_start()) + addr);
546   if (p->load() != value) {
547     // 18. If v is not equal to w, then
548     //   a. Perform LeaveCriticalSection(WL).
549     //   ...
550     //   c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
551     //   d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
552     //     "not-equal").
553     //   e. Return resultObject.
554     CHECK(
555         JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
556                                        factory->false_value(), Just(kDontThrow))
557             .FromJust());
558     CHECK(JSReceiver::CreateDataProperty(
559               isolate, result, factory->value_string(),
560               factory->not_equal_string(), Just(kDontThrow))
561               .FromJust());
562     return *result;
563   }
564 
565   if (use_timeout && rel_timeout_ns == 0) {
566     // 19. If t is 0 and mode is async, then
567     //   ...
568     //   b. Perform LeaveCriticalSection(WL).
569     //   c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
570     //   d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
571     //     "timed-out").
572     //   e. Return resultObject.
573     CHECK(
574         JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
575                                        factory->false_value(), Just(kDontThrow))
576             .FromJust());
577     CHECK(JSReceiver::CreateDataProperty(
578               isolate, result, factory->value_string(),
579               factory->timed_out_string(), Just(kDontThrow))
580               .FromJust());
581     return *result;
582   }
583 
584   Handle<JSObject> promise_capability = factory->NewJSPromise();
585   FutexWaitListNode* node =
586       new FutexWaitListNode(backing_store, addr, promise_capability, isolate);
587 
588   {
589     NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
590     g_wait_list.Pointer()->AddNode(node);
591   }
592   if (use_timeout) {
593     node->async_timeout_time_ = base::TimeTicks::Now() + rel_timeout;
594     auto task = std::make_unique<AsyncWaiterTimeoutTask>(
595         node->cancelable_task_manager_, node);
596     node->timeout_task_id_ = task->id();
597     node->task_runner_->PostNonNestableDelayedTask(std::move(task),
598                                                    rel_timeout.InSecondsF());
599   }
600 
601   // 26. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
602   // 27. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
603   // promiseCapability.[[Promise]]).
604   // 28. Return resultObject.
605   CHECK(JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
606                                        factory->true_value(), Just(kDontThrow))
607             .FromJust());
608   CHECK(JSReceiver::CreateDataProperty(isolate, result, factory->value_string(),
609                                        promise_capability, Just(kDontThrow))
610             .FromJust());
611   return *result;
612 }
613 
Wake(Handle<JSArrayBuffer> array_buffer,size_t addr,uint32_t num_waiters_to_wake)614 Object FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
615                             uint32_t num_waiters_to_wake) {
616   DCHECK_LT(addr, array_buffer->byte_length());
617 
618   int waiters_woken = 0;
619   std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
620   auto wait_location = FutexWaitList::ToWaitLocation(backing_store.get(), addr);
621 
622   NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
623 
624   auto& location_lists = g_wait_list.Pointer()->location_lists_;
625   auto it = location_lists.find(wait_location);
626   if (it == location_lists.end()) {
627     return Smi::zero();
628   }
629   FutexWaitListNode* node = it->second.head;
630   while (node && num_waiters_to_wake > 0) {
631     bool delete_this_node = false;
632     std::shared_ptr<BackingStore> node_backing_store =
633         node->backing_store_.lock();
634 
635     if (!node->waiting_) {
636       node = node->next_;
637       continue;
638     }
639     // Relying on wait_location_ here is not enough, since we need to guard
640     // against the case where the BackingStore of the node has been deleted and
641     // a new BackingStore recreated in the same memory area.
642     if (backing_store.get() == node_backing_store.get()) {
643       DCHECK_EQ(addr, node->wait_addr_);
644       node->waiting_ = false;
645 
646       // Retrieve the next node to iterate before calling NotifyAsyncWaiter,
647       // since NotifyAsyncWaiter will take the node out of the linked list.
648       auto old_node = node;
649       node = node->next_;
650       if (old_node->IsAsync()) {
651         NotifyAsyncWaiter(old_node);
652       } else {
653         // WaitSync will remove the node from the list.
654         old_node->cond_.NotifyOne();
655       }
656       if (num_waiters_to_wake != kWakeAll) {
657         --num_waiters_to_wake;
658       }
659       waiters_woken++;
660       continue;
661     }
662     DCHECK_EQ(nullptr, node_backing_store.get());
663     if (node->async_timeout_time_ == base::TimeTicks()) {
664       // Backing store has been deleted and the node is still waiting, and
665       // there's no timeout. It's never going to be woken up, so we can clean
666       // it up now. We don't need to cancel the timeout task, because there is
667       // none.
668 
669       // This cleanup code is not very efficient, since it only kicks in when
670       // a new BackingStore has been created in the same memory area where the
671       // deleted BackingStore was.
672       DCHECK(node->IsAsync());
673       DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
674       delete_this_node = true;
675     }
676     if (node->IsAsync() && node->native_context_.IsEmpty()) {
677       // The NativeContext related to the async waiter has been deleted.
678       // Ditto, clean up now.
679 
680       // Using the CancelableTaskManager here is OK since the Isolate is
681       // guaranteed to be alive - FutexEmulation::IsolateDeinit removes all
682       // FutexWaitListNodes owned by an Isolate which is going to die.
683       if (node->CancelTimeoutTask()) {
684         delete_this_node = true;
685       }
686       // If cancelling the timeout task failed, the timeout task is already
687       // running and will clean up the node.
688     }
689 
690     if (delete_this_node) {
691       auto old_node = node;
692       node = node->next_;
693       g_wait_list.Pointer()->RemoveNode(old_node);
694       DCHECK_EQ(CancelableTaskManager::kInvalidTaskId,
695                 old_node->timeout_task_id_);
696       delete old_node;
697     } else {
698       node = node->next_;
699     }
700   }
701 
702   return Smi::FromInt(waiters_woken);
703 }
704 
CleanupAsyncWaiterPromise(FutexWaitListNode * node)705 void FutexEmulation::CleanupAsyncWaiterPromise(FutexWaitListNode* node) {
706   // This function must run in the main thread of node's Isolate. This function
707   // may allocate memory. To avoid deadlocks, we shouldn't be holding g_mutex.
708 
709   DCHECK(FLAG_harmony_atomics_waitasync);
710   DCHECK(node->IsAsync());
711 
712   Isolate* isolate = node->isolate_for_async_waiters_;
713   auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
714 
715   if (!node->promise_.IsEmpty()) {
716     Handle<JSPromise> promise = Handle<JSPromise>::cast(
717         Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
718     // Promise keeps the NativeContext alive.
719     DCHECK(!node->native_context_.IsEmpty());
720     Handle<NativeContext> native_context = Handle<NativeContext>::cast(
721         Utils::OpenHandle(*node->native_context_.Get(v8_isolate)));
722 
723     // Remove the Promise from the NativeContext's set.
724     Handle<OrderedHashSet> promises(
725         native_context->atomics_waitasync_promises(), isolate);
726     bool was_deleted = OrderedHashSet::Delete(isolate, *promises, *promise);
727     DCHECK(was_deleted);
728     USE(was_deleted);
729     promises = OrderedHashSet::Shrink(isolate, promises);
730     native_context->set_atomics_waitasync_promises(*promises);
731   } else {
732     // NativeContext keeps the Promise alive; if the Promise is dead then
733     // surely NativeContext is too.
734     DCHECK(node->native_context_.IsEmpty());
735   }
736 }
737 
ResolveAsyncWaiterPromise(FutexWaitListNode * node)738 void FutexEmulation::ResolveAsyncWaiterPromise(FutexWaitListNode* node) {
739   // This function must run in the main thread of node's Isolate.
740   DCHECK(FLAG_harmony_atomics_waitasync);
741 
742   auto v8_isolate =
743       reinterpret_cast<v8::Isolate*>(node->isolate_for_async_waiters_);
744 
745   // Try to cancel the timeout task (if one exists). If the timeout task exists,
746   // cancelling it will always succeed. It's not possible for the timeout task
747   // to be running, since it's scheduled to run in the same thread as this task.
748 
749   // Using the CancelableTaskManager here is OK since the Isolate is guaranteed
750   // to be alive - FutexEmulation::IsolateDeinit removes all FutexWaitListNodes
751   // owned by an Isolate which is going to die.
752   bool success = node->CancelTimeoutTask();
753   DCHECK(success);
754   USE(success);
755 
756   if (!node->promise_.IsEmpty()) {
757     DCHECK(!node->native_context_.IsEmpty());
758     Local<v8::Context> native_context = node->native_context_.Get(v8_isolate);
759     v8::Context::Scope contextScope(native_context);
760     Handle<JSPromise> promise = Handle<JSPromise>::cast(
761         Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
762     Handle<String> result_string;
763     // When waiters are notified, their async_timeout_time_ is reset. Having a
764     // non-zero async_timeout_time_ here means the waiter timed out.
765     if (node->async_timeout_time_ != base::TimeTicks()) {
766       DCHECK(node->waiting_);
767       result_string =
768           node->isolate_for_async_waiters_->factory()->timed_out_string();
769     } else {
770       DCHECK(!node->waiting_);
771       result_string = node->isolate_for_async_waiters_->factory()->ok_string();
772     }
773     MaybeHandle<Object> resolve_result =
774         JSPromise::Resolve(promise, result_string);
775     DCHECK(!resolve_result.is_null());
776     USE(resolve_result);
777   }
778 }
779 
ResolveAsyncWaiterPromises(Isolate * isolate)780 void FutexEmulation::ResolveAsyncWaiterPromises(Isolate* isolate) {
781   // This function must run in the main thread of isolate.
782   DCHECK(FLAG_harmony_atomics_waitasync);
783 
784   FutexWaitListNode* node;
785   {
786     NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
787 
788     auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
789     auto it = isolate_map.find(isolate);
790     DCHECK_NE(isolate_map.end(), it);
791 
792     node = it->second.head;
793     isolate_map.erase(it);
794   }
795 
796   // The list of nodes starting from "node" are no longer on any list, so it's
797   // ok to iterate them without holding the mutex. We also need to not hold the
798   // mutex while calling CleanupAsyncWaiterPromise, since it may allocate
799   // memory.
800   HandleScope handle_scope(isolate);
801   while (node) {
802     DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
803     DCHECK(!node->waiting_);
804     ResolveAsyncWaiterPromise(node);
805     CleanupAsyncWaiterPromise(node);
806     // We've already tried to cancel the timeout task for the node; since we're
807     // now in the same thread the timeout task is supposed to run, we know the
808     // timeout task will never happen, and it's safe to delete the node here.
809     DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
810     node = FutexWaitList::DeleteAsyncWaiterNode(node);
811   }
812 }
813 
HandleAsyncWaiterTimeout(FutexWaitListNode * node)814 void FutexEmulation::HandleAsyncWaiterTimeout(FutexWaitListNode* node) {
815   // This function must run in the main thread of node's Isolate.
816   DCHECK(FLAG_harmony_atomics_waitasync);
817   DCHECK(node->IsAsync());
818 
819   {
820     NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
821 
822     node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
823     if (!node->waiting_) {
824       // If the Node is not waiting, it's already scheduled to have its Promise
825       // resolved. Ignore the timeout.
826       return;
827     }
828     g_wait_list.Pointer()->RemoveNode(node);
829   }
830 
831   // "node" has been taken out of the lists, so it's ok to access it without
832   // holding the mutex. We also need to not hold the mutex while calling
833   // CleanupAsyncWaiterPromise, since it may allocate memory.
834   HandleScope handle_scope(node->isolate_for_async_waiters_);
835   ResolveAsyncWaiterPromise(node);
836   CleanupAsyncWaiterPromise(node);
837   delete node;
838 }
839 
IsolateDeinit(Isolate * isolate)840 void FutexEmulation::IsolateDeinit(Isolate* isolate) {
841   NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
842 
843   // Iterate all locations to find nodes belonging to "isolate" and delete them.
844   // The Isolate is going away; don't bother cleaning up the Promises in the
845   // NativeContext. Also we don't need to cancel the timeout tasks, since they
846   // will be cancelled by Isolate::Deinit.
847   {
848     auto& location_lists = g_wait_list.Pointer()->location_lists_;
849     auto it = location_lists.begin();
850     while (it != location_lists.end()) {
851       FutexWaitListNode*& head = it->second.head;
852       FutexWaitListNode*& tail = it->second.tail;
853       FutexWaitList::DeleteNodesForIsolate(isolate, &head, &tail);
854       // head and tail are either both nullptr or both non-nullptr.
855       DCHECK_EQ(head == nullptr, tail == nullptr);
856       if (head == nullptr) {
857         location_lists.erase(it++);
858       } else {
859         ++it;
860       }
861     }
862   }
863 
864   {
865     auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
866     auto it = isolate_map.find(isolate);
867     if (it != isolate_map.end()) {
868       auto node = it->second.head;
869       while (node) {
870         DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
871         node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
872         node = FutexWaitList::DeleteAsyncWaiterNode(node);
873       }
874       isolate_map.erase(it);
875     }
876   }
877 
878   g_wait_list.Pointer()->Verify();
879 }
880 
NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,size_t addr)881 Object FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
882                                             size_t addr) {
883   DCHECK_LT(addr, array_buffer->byte_length());
884   std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
885 
886   NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
887 
888   auto wait_location = FutexWaitList::ToWaitLocation(backing_store.get(), addr);
889   auto& location_lists = g_wait_list.Pointer()->location_lists_;
890   auto it = location_lists.find(wait_location);
891   if (it == location_lists.end()) {
892     return Smi::zero();
893   }
894   int waiters = 0;
895   FutexWaitListNode* node = it->second.head;
896   while (node != nullptr) {
897     std::shared_ptr<BackingStore> node_backing_store =
898         node->backing_store_.lock();
899     if (backing_store.get() == node_backing_store.get() && node->waiting_) {
900       waiters++;
901     }
902 
903     node = node->next_;
904   }
905 
906   return Smi::FromInt(waiters);
907 }
908 
NumAsyncWaitersForTesting(Isolate * isolate)909 Object FutexEmulation::NumAsyncWaitersForTesting(Isolate* isolate) {
910   NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
911 
912   int waiters = 0;
913   for (const auto& it : g_wait_list.Pointer()->location_lists_) {
914     FutexWaitListNode* node = it.second.head;
915     while (node != nullptr) {
916       if (node->isolate_for_async_waiters_ == isolate && node->waiting_) {
917         waiters++;
918       }
919       node = node->next_;
920     }
921   }
922 
923   return Smi::FromInt(waiters);
924 }
925 
NumUnresolvedAsyncPromisesForTesting(Handle<JSArrayBuffer> array_buffer,size_t addr)926 Object FutexEmulation::NumUnresolvedAsyncPromisesForTesting(
927     Handle<JSArrayBuffer> array_buffer, size_t addr) {
928   DCHECK_LT(addr, array_buffer->byte_length());
929   std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
930 
931   NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
932 
933   int waiters = 0;
934   auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
935   for (const auto& it : isolate_map) {
936     FutexWaitListNode* node = it.second.head;
937     while (node != nullptr) {
938       std::shared_ptr<BackingStore> node_backing_store =
939           node->backing_store_.lock();
940       if (backing_store.get() == node_backing_store.get() &&
941           addr == node->wait_addr_ && !node->waiting_) {
942         waiters++;
943       }
944 
945       node = node->next_;
946     }
947   }
948 
949   return Smi::FromInt(waiters);
950 }
951 
VerifyNode(FutexWaitListNode * node,FutexWaitListNode * head,FutexWaitListNode * tail)952 void FutexWaitList::VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
953                                FutexWaitListNode* tail) {
954 #ifdef DEBUG
955   if (node->next_ != nullptr) {
956     DCHECK_NE(node, tail);
957     DCHECK_EQ(node, node->next_->prev_);
958   } else {
959     DCHECK_EQ(node, tail);
960   }
961   if (node->prev_ != nullptr) {
962     DCHECK_NE(node, head);
963     DCHECK_EQ(node, node->prev_->next_);
964   } else {
965     DCHECK_EQ(node, head);
966   }
967 
968   if (node->async_timeout_time_ != base::TimeTicks()) {
969     DCHECK(FLAG_harmony_atomics_waitasync);
970     DCHECK(node->IsAsync());
971   }
972 
973   DCHECK(NodeIsOnList(node, head));
974 #endif  // DEBUG
975 }
976 
Verify()977 void FutexWaitList::Verify() {
978 #ifdef DEBUG
979   for (const auto& it : location_lists_) {
980     FutexWaitListNode* node = it.second.head;
981     while (node != nullptr) {
982       VerifyNode(node, it.second.head, it.second.tail);
983       node = node->next_;
984     }
985   }
986 
987   for (const auto& it : isolate_promises_to_resolve_) {
988     auto node = it.second.head;
989     while (node != nullptr) {
990       VerifyNode(node, it.second.head, it.second.tail);
991       DCHECK_EQ(it.first, node->isolate_for_async_waiters_);
992       node = node->next_;
993     }
994   }
995 #endif  // DEBUG
996 }
997 
NodeIsOnList(FutexWaitListNode * node,FutexWaitListNode * head)998 bool FutexWaitList::NodeIsOnList(FutexWaitListNode* node,
999                                  FutexWaitListNode* head) {
1000   auto n = head;
1001   while (n != nullptr) {
1002     if (n == node) {
1003       return true;
1004     }
1005     n = n->next_;
1006   }
1007   return false;
1008 }
1009 
1010 }  // namespace internal
1011 }  // namespace v8
1012