[Atomics.waitAsync] Implement Atomics.waitAsync

Original design doc:
https://docs.google.com/document/d/1dthXsVHMc1Sd_oYf9a-KZSFOd_a8dUgnt4REAG8YIXA

Design changes:
https://docs.google.com/document/d/1aeEGDm1XSqoJkQQKz9F75WqnuAa2caktxGy_O_KpO9Y


Bug: v8:10239
Change-Id: Iab94ccab85d7b4ff23cff1955774b42edf5be541
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2202981
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68844}
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index a963cc7..61b0b49 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -746,6 +746,7 @@
   CPP(AtomicsNotify)                                                           \
   CPP(AtomicsIsLockFree)                                                       \
   CPP(AtomicsWait)                                                             \
+  CPP(AtomicsWaitAsync)                                                        \
   CPP(AtomicsWake)                                                             \
                                                                                \
   /* String */                                                                 \
diff --git a/src/builtins/builtins-sharedarraybuffer.cc b/src/builtins/builtins-sharedarraybuffer.cc
index f89bc25..6c9540c 100644
--- a/src/builtins/builtins-sharedarraybuffer.cc
+++ b/src/builtins/builtins-sharedarraybuffer.cc
@@ -177,25 +177,22 @@
   RETURN_RESULT_OR_FAILURE(isolate, AtomicsWake(isolate, array, index, count));
 }
 
-// ES #sec-atomics.wait
-// Atomics.wait( typedArray, index, value, timeout )
-BUILTIN(AtomicsWait) {
-  HandleScope scope(isolate);
-  Handle<Object> array = args.atOrUndefined(isolate, 1);
-  Handle<Object> index = args.atOrUndefined(isolate, 2);
-  Handle<Object> value = args.atOrUndefined(isolate, 3);
-  Handle<Object> timeout = args.atOrUndefined(isolate, 4);
-
+Object DoWait(Isolate* isolate, FutexEmulation::WaitMode mode,
+              Handle<Object> array, Handle<Object> index, Handle<Object> value,
+              Handle<Object> timeout) {
+  // 1. Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
   Handle<JSTypedArray> sta;
   ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
       isolate, sta, ValidateSharedIntegerTypedArray(isolate, array, true));
 
+  // 2. Let i be ? ValidateAtomicAccess(typedArray, index).
   Maybe<size_t> maybe_index = ValidateAtomicAccess(isolate, sta, index);
   if (maybe_index.IsNothing()) return ReadOnlyRoots(isolate).exception();
   size_t i = maybe_index.FromJust();
 
-  // According to the spec, we have to check value's type before
-  // looking at the timeout.
+  // 3. Let arrayTypeName be typedArray.[[TypedArrayName]].
+  // 4. If arrayTypeName is "BigInt64Array", let v be ? ToBigInt64(value).
+  // 5. Otherwise, let v be ? ToInt32(value).
   if (sta->type() == kExternalBigInt64Array) {
     ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value,
                                        BigInt::FromObject(isolate, value));
@@ -205,6 +202,8 @@
                                        Object::ToInt32(isolate, value));
   }
 
+  // 6. Let q be ? ToNumber(timeout).
+  // 7. If q is NaN, let t be +∞, else let t be max(q, 0).
   double timeout_number;
   if (timeout->IsUndefined(isolate)) {
     timeout_number = ReadOnlyRoots(isolate).infinity_value().Number();
@@ -218,7 +217,11 @@
       timeout_number = 0;
   }
 
-  if (!isolate->allow_atomics_wait()) {
+  // 8. If mode is sync, then
+  //   a. Let B be AgentCanSuspend().
+  //   b. If B is false, throw a TypeError exception.
+  if (mode == FutexEmulation::WaitMode::kSync &&
+      !isolate->allow_atomics_wait()) {
     THROW_NEW_ERROR_RETURN_FAILURE(
         isolate, NewTypeError(MessageTemplate::kAtomicsWaitNotAllowed));
   }
@@ -227,15 +230,39 @@
 
   if (sta->type() == kExternalBigInt64Array) {
     return FutexEmulation::WaitJs64(
-        isolate, array_buffer, GetAddress64(i, sta->byte_offset()),
+        isolate, mode, array_buffer, GetAddress64(i, sta->byte_offset()),
         Handle<BigInt>::cast(value)->AsInt64(), timeout_number);
   } else {
     DCHECK(sta->type() == kExternalInt32Array);
-    return FutexEmulation::WaitJs32(isolate, array_buffer,
+    return FutexEmulation::WaitJs32(isolate, mode, array_buffer,
                                     GetAddress32(i, sta->byte_offset()),
                                     NumberToInt32(*value), timeout_number);
   }
 }
 
+// ES #sec-atomics.wait
+// Atomics.wait( typedArray, index, value, timeout )
+BUILTIN(AtomicsWait) {
+  HandleScope scope(isolate);
+  Handle<Object> array = args.atOrUndefined(isolate, 1);
+  Handle<Object> index = args.atOrUndefined(isolate, 2);
+  Handle<Object> value = args.atOrUndefined(isolate, 3);
+  Handle<Object> timeout = args.atOrUndefined(isolate, 4);
+
+  return DoWait(isolate, FutexEmulation::WaitMode::kSync, array, index, value,
+                timeout);
+}
+
+BUILTIN(AtomicsWaitAsync) {
+  HandleScope scope(isolate);
+  Handle<Object> array = args.atOrUndefined(isolate, 1);
+  Handle<Object> index = args.atOrUndefined(isolate, 2);
+  Handle<Object> value = args.atOrUndefined(isolate, 3);
+  Handle<Object> timeout = args.atOrUndefined(isolate, 4);
+
+  return DoWait(isolate, FutexEmulation::WaitMode::kAsync, array, index, value,
+                timeout);
+}
+
 }  // namespace internal
 }  // namespace v8
diff --git a/src/execution/futex-emulation.cc b/src/execution/futex-emulation.cc
index dd718f1..c88fa3f 100644
--- a/src/execution/futex-emulation.cc
+++ b/src/execution/futex-emulation.cc
@@ -6,15 +6,18 @@
 
 #include <limits>
 
+#include "src/api/api-inl.h"
+#include "src/base/logging.h"
 #include "src/base/macros.h"
-#include "src/base/platform/time.h"
 #include "src/execution/isolate.h"
 #include "src/execution/vm-state-inl.h"
 #include "src/handles/handles-inl.h"
 #include "src/numbers/conversions.h"
 #include "src/objects/bigint.h"
 #include "src/objects/js-array-buffer-inl.h"
+#include "src/objects/js-promise-inl.h"
 #include "src/objects/objects-inl.h"
+#include "src/tasks/cancelable-task.h"
 
 namespace v8 {
 namespace internal {
@@ -25,7 +28,22 @@
 base::LazyInstance<FutexWaitList>::type FutexEmulation::wait_list_ =
     LAZY_INSTANCE_INITIALIZER;
 
+FutexWaitListNode::~FutexWaitListNode() {
+  // Assert that the timeout task was cancelled.
+  DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, timeout_task_id_);
+}
+
+bool FutexWaitListNode::CancelTimeoutTask() {
+  if (timeout_task_id_ != CancelableTaskManager::kInvalidTaskId) {
+    auto return_value = cancelable_task_manager_->TryAbort(timeout_task_id_);
+    timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
+    return return_value != TryAbortResult::kTaskRunning;
+  }
+  return true;
+}
+
 void FutexWaitListNode::NotifyWake() {
+  DCHECK(!IsAsync());
   // Lock the FutexEmulation mutex before notifying. We know that the mutex
   // will have been unlocked if we are currently waiting on the condition
   // variable. The mutex will not be locked if FutexEmulation::Wait hasn't
@@ -37,10 +55,75 @@
   interrupted_ = true;
 }
 
-FutexWaitList::FutexWaitList() : head_(nullptr), tail_(nullptr) {}
+class ResolveAsyncWaiterPromisesTask : public CancelableTask {
+ public:
+  ResolveAsyncWaiterPromisesTask(CancelableTaskManager* cancelable_task_manager,
+                                 Isolate* isolate)
+      : CancelableTask(cancelable_task_manager), isolate_(isolate) {}
+
+  void RunInternal() override {
+    FutexEmulation::ResolveAsyncWaiterPromises(isolate_);
+  }
+
+ private:
+  Isolate* isolate_;
+};
+
+class AsyncWaiterTimeoutTask : public CancelableTask {
+ public:
+  AsyncWaiterTimeoutTask(CancelableTaskManager* cancelable_task_manager,
+                         FutexWaitListNode* node)
+      : CancelableTask(cancelable_task_manager), node_(node) {}
+
+  void RunInternal() override {
+    FutexEmulation::HandleAsyncWaiterTimeout(node_);
+  }
+
+ private:
+  FutexWaitListNode* node_;
+};
+
+void FutexEmulation::NotifyAsyncWaiter(FutexWaitListNode* node) {
+  // This function can run in any thread.
+
+  FutexEmulation::mutex_.Pointer()->AssertHeld();
+
+  // Nullify the timeout time; this distinguishes timed out waiters from
+  // woken up ones.
+  node->async_timeout_time_ = base::TimeTicks();
+  // Try to cancel the timeout task. If cancelling fails, the task is already
+  // running. In that case, it cannot proceed beyond waiting for the mutex,
+  // since we're holding it. When it gets the mutex, it will see that waiting_
+  // is false, and ignore the FutexWaitListNode.
+
+  // Using the CancelableTaskManager here is OK since the Isolate is guaranteed
+  // to be alive - FutexEmulation::IsolateDeinit removes all FutexWaitListNodes
+  // owned by an Isolate which is going to die.
+  node->CancelTimeoutTask();
+
+  wait_list_.Pointer()->RemoveNode(node);
+
+  // Schedule a task for resolving the Promise.
+  auto& isolate_map = wait_list_.Pointer()->isolate_promises_to_resolve_;
+  auto it = isolate_map.find(node->isolate_for_async_waiters_);
+  if (it == isolate_map.end()) {
+    // This Isolate doesn't have other Promises to resolve at the moment.
+    isolate_map.insert(std::make_pair(node->isolate_for_async_waiters_,
+                                      FutexWaitList::HeadAndTail{node, node}));
+    auto task = std::make_unique<ResolveAsyncWaiterPromisesTask>(
+        node->cancelable_task_manager_, node->isolate_for_async_waiters_);
+    node->task_runner_->PostNonNestableTask(std::move(task));
+  } else {
+    // Add this Node into the existing list.
+    node->prev_ = it->second.tail;
+    it->second.tail->next_ = node;
+    it->second.tail = node;
+  }
+}
 
 void FutexWaitList::AddNode(FutexWaitListNode* node) {
-  DCHECK(node->prev_ == nullptr && node->next_ == nullptr);
+  DCHECK_NULL(node->prev_);
+  DCHECK_NULL(node->next_);
   if (tail_) {
     tail_->next_ = node;
   } else {
@@ -48,24 +131,31 @@
   }
 
   node->prev_ = tail_;
-  node->next_ = nullptr;
   tail_ = node;
+
+  Verify();
 }
 
 void FutexWaitList::RemoveNode(FutexWaitListNode* node) {
+  DCHECK(NodeIsOnList(node, head_));
+
   if (node->prev_) {
     node->prev_->next_ = node->next_;
   } else {
+    DCHECK_EQ(node, head_);
     head_ = node->next_;
   }
 
   if (node->next_) {
     node->next_->prev_ = node->prev_;
   } else {
+    DCHECK_EQ(node, tail_);
     tail_ = node->prev_;
   }
 
   node->prev_ = node->next_ = nullptr;
+
+  Verify();
 }
 
 void AtomicsWaitWakeHandle::Wake() {
@@ -103,19 +193,19 @@
 
 }  // namespace
 
-Object FutexEmulation::WaitJs32(Isolate* isolate,
+Object FutexEmulation::WaitJs32(Isolate* isolate, WaitMode mode,
                                 Handle<JSArrayBuffer> array_buffer, size_t addr,
                                 int32_t value, double rel_timeout_ms) {
   Object res =
-      Wait<int32_t>(isolate, array_buffer, addr, value, rel_timeout_ms);
+      Wait<int32_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
   return WaitJsTranslateReturn(isolate, res);
 }
 
-Object FutexEmulation::WaitJs64(Isolate* isolate,
+Object FutexEmulation::WaitJs64(Isolate* isolate, WaitMode mode,
                                 Handle<JSArrayBuffer> array_buffer, size_t addr,
                                 int64_t value, double rel_timeout_ms) {
   Object res =
-      Wait<int64_t>(isolate, array_buffer, addr, value, rel_timeout_ms);
+      Wait<int64_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
   return WaitJsTranslateReturn(isolate, res);
 }
 
@@ -123,20 +213,20 @@
                                   Handle<JSArrayBuffer> array_buffer,
                                   size_t addr, int32_t value,
                                   int64_t rel_timeout_ns) {
-  return Wait<int32_t>(isolate, array_buffer, addr, value, rel_timeout_ns >= 0,
-                       rel_timeout_ns);
+  return Wait<int32_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
+                       rel_timeout_ns >= 0, rel_timeout_ns);
 }
 
 Object FutexEmulation::WaitWasm64(Isolate* isolate,
                                   Handle<JSArrayBuffer> array_buffer,
                                   size_t addr, int64_t value,
                                   int64_t rel_timeout_ns) {
-  return Wait<int64_t>(isolate, array_buffer, addr, value, rel_timeout_ns >= 0,
-                       rel_timeout_ns);
+  return Wait<int64_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
+                       rel_timeout_ns >= 0, rel_timeout_ns);
 }
 
 template <typename T>
-Object FutexEmulation::Wait(Isolate* isolate,
+Object FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
                             Handle<JSArrayBuffer> array_buffer, size_t addr,
                             T value, double rel_timeout_ms) {
   DCHECK_LT(addr, array_buffer->byte_length());
@@ -157,7 +247,8 @@
       rel_timeout_ns = static_cast<int64_t>(timeout_ns);
     }
   }
-  return Wait(isolate, array_buffer, addr, value, use_timeout, rel_timeout_ns);
+  return Wait(isolate, mode, array_buffer, addr, value, use_timeout,
+              rel_timeout_ns);
 }
 
 namespace {
@@ -170,9 +261,23 @@
 }  // namespace
 
 template <typename T>
-Object FutexEmulation::Wait(Isolate* isolate,
+Object FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
                             Handle<JSArrayBuffer> array_buffer, size_t addr,
                             T value, bool use_timeout, int64_t rel_timeout_ns) {
+  if (mode == WaitMode::kSync) {
+    return WaitSync(isolate, array_buffer, addr, value, use_timeout,
+                    rel_timeout_ns);
+  }
+  DCHECK_EQ(mode, WaitMode::kAsync);
+  return WaitAsync(isolate, array_buffer, addr, value, use_timeout,
+                   rel_timeout_ns);
+}
+
+template <typename T>
+Object FutexEmulation::WaitSync(Isolate* isolate,
+                                Handle<JSArrayBuffer> array_buffer, size_t addr,
+                                T value, bool use_timeout,
+                                int64_t rel_timeout_ns) {
   VMState<ATOMICS_WAIT> state(isolate);
   base::TimeDelta rel_timeout =
       base::TimeDelta::FromNanoseconds(rel_timeout_ns);
@@ -305,6 +410,119 @@
   return *result;
 }
 
+FutexWaitListNode::FutexWaitListNode(
+    const std::shared_ptr<BackingStore>& backing_store, size_t wait_addr,
+    Handle<JSObject> promise, Isolate* isolate)
+    : isolate_for_async_waiters_(isolate),
+      backing_store_(backing_store),
+      wait_addr_(wait_addr),
+      waiting_(true) {
+  auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
+  task_runner_ = V8::GetCurrentPlatform()->GetForegroundTaskRunner(v8_isolate);
+  cancelable_task_manager_ = isolate->cancelable_task_manager();
+
+  v8::Local<v8::Promise> local_promise = Utils::PromiseToLocal(promise);
+  promise_.Reset(v8_isolate, local_promise);
+  promise_.SetWeak();
+  Handle<NativeContext> native_context(isolate->native_context());
+  v8::Local<v8::Context> local_native_context =
+      Utils::ToLocal(Handle<Context>::cast(native_context));
+  native_context_.Reset(v8_isolate, local_native_context);
+  native_context_.SetWeak();
+
+  // Add the Promise into the NativeContext's atomics_waitasync_promises set, so
+  // that the list keeps it alive.
+  Handle<OrderedHashSet> promises(native_context->atomics_waitasync_promises(),
+                                  isolate);
+  promises = OrderedHashSet::Add(isolate, promises, promise).ToHandleChecked();
+  native_context->set_atomics_waitasync_promises(*promises);
+}
+
+template <typename T>
+Object FutexEmulation::WaitAsync(Isolate* isolate,
+                                 Handle<JSArrayBuffer> array_buffer,
+                                 size_t addr, T value, bool use_timeout,
+                                 int64_t rel_timeout_ns) {
+  DCHECK(FLAG_harmony_atomics_waitasync);
+  base::TimeDelta rel_timeout =
+      base::TimeDelta::FromNanoseconds(rel_timeout_ns);
+
+  Factory* factory = isolate->factory();
+  Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
+
+  std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
+
+  // 17. Let w be ! AtomicLoad(typedArray, i).
+  std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(
+      static_cast<int8_t*>(backing_store->buffer_start()) + addr);
+  if (p->load() != value) {
+    // 18. If v is not equal to w, then
+    //   a. Perform LeaveCriticalSection(WL).
+    //   ...
+    //   c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
+    //   d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
+    //     "not-equal").
+    //   e. Return resultObject.
+    CHECK(
+        JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
+                                       factory->false_value(), Just(kDontThrow))
+            .FromJust());
+    CHECK(JSReceiver::CreateDataProperty(
+              isolate, result, factory->value_string(),
+              factory->not_equal_string(), Just(kDontThrow))
+              .FromJust());
+    return *result;
+  }
+
+  if (use_timeout && rel_timeout_ns == 0) {
+    // 19. If t is 0 and mode is async, then
+    //   ...
+    //   b. Perform LeaveCriticalSection(WL).
+    //   c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
+    //   d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
+    //     "timed-out").
+    //   e. Return resultObject.
+    CHECK(
+        JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
+                                       factory->false_value(), Just(kDontThrow))
+            .FromJust());
+    CHECK(JSReceiver::CreateDataProperty(
+              isolate, result, factory->value_string(),
+              factory->timed_out_string(), Just(kDontThrow))
+              .FromJust());
+    return *result;
+  }
+
+  Handle<JSObject> promise_capability = factory->NewJSPromise();
+  FutexWaitListNode* node =
+      new FutexWaitListNode(backing_store, addr, promise_capability, isolate);
+
+  {
+    base::MutexGuard lock_guard(mutex_.Pointer());
+    wait_list_.Pointer()->AddNode(node);
+  }
+  if (use_timeout) {
+    node->async_timeout_time_ = base::TimeTicks::Now() + rel_timeout;
+    auto task = std::make_unique<AsyncWaiterTimeoutTask>(
+        node->cancelable_task_manager_, node);
+    node->timeout_task_id_ = task->id();
+    node->task_runner_->PostNonNestableDelayedTask(std::move(task),
+                                                   rel_timeout.InSecondsF());
+  }
+
+  // 26. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
+  // 27. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
+  // promiseCapability.[[Promise]]).
+  // 28. Return resultObject.
+  CHECK(JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
+                                       factory->true_value(), Just(kDontThrow))
+            .FromJust());
+  CHECK(JSReceiver::CreateDataProperty(isolate, result, factory->value_string(),
+                                       promise_capability, Just(kDontThrow))
+            .FromJust());
+  return *result;
+}
+
 Object FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
                             uint32_t num_waiters_to_wake) {
   DCHECK_LT(addr, array_buffer->byte_length());
@@ -315,25 +533,223 @@
   base::MutexGuard lock_guard(mutex_.Pointer());
   FutexWaitListNode* node = wait_list_.Pointer()->head_;
   while (node && num_waiters_to_wake > 0) {
+    bool delete_this_node = false;
     std::shared_ptr<BackingStore> node_backing_store =
         node->backing_store_.lock();
-    DCHECK(node_backing_store);
+
+    if (!node->waiting_) {
+      node = node->next_;
+      continue;
+    }
     if (backing_store.get() == node_backing_store.get() &&
-        addr == node->wait_addr_ && node->waiting_) {
+        addr == node->wait_addr_) {
       node->waiting_ = false;
-      node->cond_.NotifyOne();
+
+      // Retrieve the next node to iterate before calling NotifyAsyncWaiter,
+      // since NotifyAsyncWaiter will take the node out of the linked list.
+      auto old_node = node;
+      node = node->next_;
+      if (old_node->IsAsync()) {
+        NotifyAsyncWaiter(old_node);
+      } else {
+        old_node->cond_.NotifyOne();
+      }
       if (num_waiters_to_wake != kWakeAll) {
         --num_waiters_to_wake;
       }
       waiters_woken++;
+      continue;
+    }
+    if (node_backing_store.get() == nullptr &&
+        node->async_timeout_time_ == base::TimeTicks()) {
+      // Backing store has been deleted and the node is still waiting, and
+      // there's no timeout. It's never going to be woken up, so we can clean
+      // it up now. We don't need to cancel the timeout task, because there is
+      // none.
+      DCHECK(node->IsAsync());
+      DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
+      delete_this_node = true;
+    } else if (node->IsAsync() && node->native_context_.IsEmpty()) {
+      // The NativeContext related to the async waiter has been deleted.
+      // Ditto, clean up now.
+
+      // Using the CancelableTaskManager here is OK since the Isolate is
+      // guaranteed to be alive - FutexEmulation::IsolateDeinit removes all
+      // FutexWaitListNodes owned by an Isolate which is going to die.
+      if (node->CancelTimeoutTask()) {
+        delete_this_node = true;
+      }
+      // If cancelling the timeout task failed, the timeout task is already
+      // running and will clean up the node.
     }
 
-    node = node->next_;
+    if (delete_this_node) {
+      auto old_node = node;
+      node = node->next_;
+      wait_list_.Pointer()->RemoveNode(old_node);
+      delete old_node;
+    } else {
+      node = node->next_;
+    }
   }
 
   return Smi::FromInt(waiters_woken);
 }
 
+void FutexEmulation::CleanupAsyncWaiterPromise(FutexWaitListNode* node) {
+  DCHECK(FLAG_harmony_atomics_waitasync);
+  DCHECK(node->IsAsync());
+
+  Isolate* isolate = node->isolate_for_async_waiters_;
+  auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
+
+  // This function must run in the main thread of node's Isolate.
+  DCHECK_EQ(isolate->thread_id(), ThreadId::Current());
+
+  if (!node->promise_.IsEmpty()) {
+    Handle<JSPromise> promise = Handle<JSPromise>::cast(
+        Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
+    // Promise keeps the NativeContext alive.
+    DCHECK(!node->native_context_.IsEmpty());
+    Handle<NativeContext> native_context = Handle<NativeContext>::cast(
+        Utils::OpenHandle(*node->native_context_.Get(v8_isolate)));
+
+    // Remove the Promise from the NativeContext's set.
+    Handle<OrderedHashSet> promises(
+        native_context->atomics_waitasync_promises(), isolate);
+    bool was_deleted = OrderedHashSet::Delete(isolate, *promises, *promise);
+    DCHECK(was_deleted);
+    USE(was_deleted);
+    promises = OrderedHashSet::Shrink(isolate, promises);
+    native_context->set_atomics_waitasync_promises(*promises);
+  } else {
+    // NativeContext keeps the Promise alive; if the Promise is dead then
+    // surely NativeContext is too.
+    DCHECK(node->native_context_.IsEmpty());
+  }
+}
+
+FutexWaitListNode* FutexEmulation::DeleteAsyncWaiterNode(
+    FutexWaitListNode* node) {
+  auto next = node->next_;
+  delete node;
+  return next;
+}
+
+void FutexEmulation::ResolveAsyncWaiterPromise(FutexWaitListNode* node) {
+  DCHECK(FLAG_harmony_atomics_waitasync);
+
+  // This function must run in the main thread of node's Isolate.
+  DCHECK_EQ(node->isolate_for_async_waiters_->thread_id(), ThreadId::Current());
+
+  auto v8_isolate =
+      reinterpret_cast<v8::Isolate*>(node->isolate_for_async_waiters_);
+
+  if (!node->promise_.IsEmpty()) {
+    Handle<JSPromise> promise = Handle<JSPromise>::cast(
+        Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
+    Handle<String> result_string;
+    // When waiters are notified, their async_timeout_time_ is reset. Having a
+    // non-zero async_timeout_time_ here means the waiter timed out.
+    if (node->async_timeout_time_ != base::TimeTicks()) {
+      DCHECK(node->waiting_);
+      result_string =
+          node->isolate_for_async_waiters_->factory()->timed_out_string();
+    } else {
+      DCHECK(!node->waiting_);
+      result_string = node->isolate_for_async_waiters_->factory()->ok_string();
+    }
+    MaybeHandle<Object> resolve_result =
+        JSPromise::Resolve(promise, result_string);
+    DCHECK(!resolve_result.is_null());
+    USE(resolve_result);
+  }
+}
+
+void FutexEmulation::ResolveAsyncWaiterPromises(Isolate* isolate) {
+  DCHECK(FLAG_harmony_atomics_waitasync);
+
+  // This function must run in the main thread of isolate.
+  DCHECK_EQ(isolate->thread_id(), ThreadId::Current());
+
+  base::MutexGuard lock_guard(mutex_.Pointer());
+  FutexWaitListNode* node;
+  {
+    auto& isolate_map = wait_list_.Pointer()->isolate_promises_to_resolve_;
+    auto it = isolate_map.find(isolate);
+    DCHECK_NE(isolate_map.end(), it);
+
+    node = it->second.head;
+    isolate_map.erase(it);
+  }
+
+  HandleScope handle_scope(isolate);
+  while (node) {
+    DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
+    DCHECK(!node->waiting_);
+    ResolveAsyncWaiterPromise(node);
+    CleanupAsyncWaiterPromise(node);
+    // We've already tried to cancel the timeout task for the node; since we're
+    // now in the same thread the timeout task is supposed to run, we know the
+    // timeout task will never happen, and it's safe to delete the node here.
+    DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
+    node = DeleteAsyncWaiterNode(node);
+  }
+}
+
+void FutexEmulation::HandleAsyncWaiterTimeout(FutexWaitListNode* node) {
+  DCHECK(FLAG_harmony_atomics_waitasync);
+  DCHECK(node->IsAsync());
+
+  // This function must run in the main thread of node's Isolate.
+  DCHECK_EQ(node->isolate_for_async_waiters_->thread_id(), ThreadId::Current());
+
+  base::MutexGuard lock_guard(mutex_.Pointer());
+
+  if (!node->waiting_) {
+    // If the Node is not waiting, it's already scheduled to have its Promise
+    // resolved. Ignore the timeout.
+    return;
+  }
+  node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
+  wait_list_.Pointer()->RemoveNode(node);
+  HandleScope handle_scope(node->isolate_for_async_waiters_);
+  ResolveAsyncWaiterPromise(node);
+  CleanupAsyncWaiterPromise(node);
+  delete node;
+}
+
+void FutexEmulation::IsolateDeinit(Isolate* isolate) {
+  base::MutexGuard lock_guard(mutex_.Pointer());
+
+  FutexWaitListNode* node = wait_list_.Pointer()->head_;
+  while (node) {
+    if (node->isolate_for_async_waiters_ == isolate) {
+      // The Isolate is going away; don't bother cleaning up the Promises in the
+      // NativeContext. Also we don't need to cancel the timeout task, since it
+      // will be cancelled by Isolate::Deinit.
+      node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
+      wait_list_.Pointer()->RemoveNode(node);
+      node = DeleteAsyncWaiterNode(node);
+    } else {
+      node = node->next_;
+    }
+  }
+
+  auto& isolate_map = wait_list_.Pointer()->isolate_promises_to_resolve_;
+  auto it = isolate_map.find(isolate);
+  if (it != isolate_map.end()) {
+    node = it->second.head;
+    while (node) {
+      DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
+      node = DeleteAsyncWaiterNode(node);
+    }
+    isolate_map.erase(it);
+  }
+
+  wait_list_.Pointer()->Verify();
+}
+
 Object FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
                                             size_t addr) {
   DCHECK_LT(addr, array_buffer->byte_length());
@@ -346,7 +762,6 @@
   while (node) {
     std::shared_ptr<BackingStore> node_backing_store =
         node->backing_store_.lock();
-    DCHECK(node_backing_store);
     if (backing_store.get() == node_backing_store.get() &&
         addr == node->wait_addr_ && node->waiting_) {
       waiters++;
@@ -358,5 +773,103 @@
   return Smi::FromInt(waiters);
 }
 
+Object FutexEmulation::NumAsyncWaitersForTesting(Isolate* isolate) {
+  base::MutexGuard lock_guard(mutex_.Pointer());
+
+  int waiters = 0;
+  FutexWaitListNode* node = wait_list_.Pointer()->head_;
+  while (node) {
+    if (node->isolate_for_async_waiters_ == isolate && node->waiting_) {
+      waiters++;
+    }
+    node = node->next_;
+  }
+
+  return Smi::FromInt(waiters);
+}
+
+Object FutexEmulation::NumUnresolvedAsyncPromisesForTesting(
+    Handle<JSArrayBuffer> array_buffer, size_t addr) {
+  DCHECK_LT(addr, array_buffer->byte_length());
+  std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
+
+  base::MutexGuard lock_guard(mutex_.Pointer());
+
+  int waiters = 0;
+
+  auto& isolate_map = wait_list_.Pointer()->isolate_promises_to_resolve_;
+  for (auto it : isolate_map) {
+    FutexWaitListNode* node = it.second.head;
+    while (node) {
+      std::shared_ptr<BackingStore> node_backing_store =
+          node->backing_store_.lock();
+      if (backing_store.get() == node_backing_store.get() &&
+          addr == node->wait_addr_ && !node->waiting_) {
+        waiters++;
+      }
+
+      node = node->next_;
+    }
+  }
+
+  return Smi::FromInt(waiters);
+}
+
+void FutexWaitList::VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
+                               FutexWaitListNode* tail) {
+#ifdef DEBUG
+  if (node->next_) {
+    DCHECK_NE(node, tail);
+    DCHECK_EQ(node, node->next_->prev_);
+  } else {
+    DCHECK_EQ(node, tail);
+  }
+  if (node->prev_) {
+    DCHECK_NE(node, head);
+    DCHECK_EQ(node, node->prev_->next_);
+  } else {
+    DCHECK_EQ(node, head);
+  }
+
+  if (node->async_timeout_time_ != base::TimeTicks()) {
+    DCHECK(FLAG_harmony_atomics_waitasync);
+    DCHECK(node->IsAsync());
+  }
+
+  DCHECK(NodeIsOnList(node, head));
+#endif  // DEBUG
+}
+
+void FutexWaitList::Verify() {
+#ifdef DEBUG
+  FutexWaitListNode* node = head_;
+  while (node) {
+    VerifyNode(node, head_, tail_);
+    node = node->next_;
+  }
+
+  for (auto it : isolate_promises_to_resolve_) {
+    auto node = it.second.head;
+    while (node) {
+      VerifyNode(node, it.second.head, it.second.tail);
+      DCHECK_EQ(it.first, node->isolate_for_async_waiters_);
+      node = node->next_;
+    }
+  }
+#endif  // DEBUG
+}
+
+bool FutexWaitList::NodeIsOnList(FutexWaitListNode* node,
+                                 FutexWaitListNode* head) {
+  auto n = head;
+  while (n != nullptr) {
+    if (n == node) {
+      return true;
+    }
+    n = n->next_;
+  }
+  return false;
+}
+
 }  // namespace internal
 }  // namespace v8
diff --git a/src/execution/futex-emulation.h b/src/execution/futex-emulation.h
index 03ad310..2a8ba31 100644
--- a/src/execution/futex-emulation.h
+++ b/src/execution/futex-emulation.h
@@ -7,11 +7,16 @@
 
 #include <stdint.h>
 
+#include <map>
+
+#include "include/v8.h"
 #include "src/base/atomicops.h"
 #include "src/base/lazy-instance.h"
 #include "src/base/macros.h"
 #include "src/base/platform/condition-variable.h"
 #include "src/base/platform/mutex.h"
+#include "src/base/platform/time.h"
+#include "src/tasks/cancelable-task.h"
 #include "src/utils/allocation.h"
 
 // Support for emulating futexes, a low-level synchronization primitive. They
@@ -50,47 +55,93 @@
 
 class FutexWaitListNode {
  public:
-  FutexWaitListNode()
-      : prev_(nullptr),
-        next_(nullptr),
-        wait_addr_(0),
-        waiting_(false),
-        interrupted_(false) {}
+  // Create a sync FutexWaitListNode.
+  FutexWaitListNode() = default;
+
+  // Create an async FutexWaitListNode.
+  FutexWaitListNode(const std::shared_ptr<BackingStore>& backing_store,
+                    size_t wait_addr, Handle<JSObject> promise_capability,
+                    Isolate* isolate);
+  ~FutexWaitListNode();
 
   void NotifyWake();
 
+  bool IsAsync() const { return isolate_for_async_waiters_ != nullptr; }
+
+  // Returns false if the cancelling failed, true otherwise.
+  bool CancelTimeoutTask();
+
  private:
   friend class FutexEmulation;
   friend class FutexWaitList;
   friend class ResetWaitingOnScopeExit;
 
+  // Set only for async FutexWaitListNodes.
+  Isolate* isolate_for_async_waiters_ = nullptr;
+  std::shared_ptr<TaskRunner> task_runner_;
+  CancelableTaskManager* cancelable_task_manager_ = nullptr;
+
   base::ConditionVariable cond_;
   // prev_ and next_ are protected by FutexEmulation::mutex_.
-  FutexWaitListNode* prev_;
-  FutexWaitListNode* next_;
+  FutexWaitListNode* prev_ = nullptr;
+  FutexWaitListNode* next_ = nullptr;
+
   std::weak_ptr<BackingStore> backing_store_;
-  size_t wait_addr_;
+  size_t wait_addr_ = 0;
   // waiting_ and interrupted_ are protected by FutexEmulation::mutex_
   // if this node is currently contained in FutexEmulation::wait_list_
   // or an AtomicsWaitWakeHandle has access to it.
-  bool waiting_;
-  bool interrupted_;
+  bool waiting_ = false;
+  bool interrupted_ = false;
+
+  // Only for async FutexWaitListNodes. Weak Global handle. Must not be
+  // synchronously resolved by a non-owner Isolate.
+  v8::Global<v8::Promise> promise_;
+
+  // Only for async FutexWaitListNodes. Weak Global handle.
+  v8::Global<v8::Context> native_context_;
+
+  // Only for async FutexWaitListNodes. If async_timeout_time_ is
+  // base::TimeTicks(), this async waiter doesn't have a timeout or has already
+  // been notified. Values other than base::TimeTicks() are used for async
+  // waiters with an active timeout.
+  base::TimeTicks async_timeout_time_;
+
+  CancelableTaskManager::Id timeout_task_id_ =
+      CancelableTaskManager::kInvalidTaskId;
 
   DISALLOW_COPY_AND_ASSIGN(FutexWaitListNode);
 };
 
 class FutexWaitList {
  public:
-  FutexWaitList();
+  FutexWaitList() = default;
 
   void AddNode(FutexWaitListNode* node);
   void RemoveNode(FutexWaitListNode* node);
 
+  // For checking the internal consistency of the FutexWaitList.
+  void Verify();
+  // Verifies the local consistency of |node|. If it's the first node of its
+  // list, it must be |head|, and if it's the last node, it must be |tail|.
+  void VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
+                  FutexWaitListNode* tail);
+  // Returns true if |node| is on the linked list starting with |head|.
+  static bool NodeIsOnList(FutexWaitListNode* node, FutexWaitListNode* head);
+
  private:
   friend class FutexEmulation;
 
-  FutexWaitListNode* head_;
-  FutexWaitListNode* tail_;
+  FutexWaitListNode* head_ = nullptr;
+  FutexWaitListNode* tail_ = nullptr;
+
+  struct HeadAndTail {
+    FutexWaitListNode* head;
+    FutexWaitListNode* tail;
+  };
+  // Isolate* -> linked list of Nodes which are waiting for their Promises to
+  // be resolved.
+  std::map<Isolate*, HeadAndTail> isolate_promises_to_resolve_;
 
   DISALLOW_COPY_AND_ASSIGN(FutexWaitList);
 };
@@ -108,6 +159,8 @@
 
 class FutexEmulation : public AllStatic {
  public:
+  enum WaitMode { kSync = 0, kAsync };
+
   // Pass to Wake() to wake all waiters.
   static const uint32_t kWakeAll = UINT32_MAX;
 
@@ -117,12 +170,14 @@
   // |rel_timeout_ms| can be Infinity.
   // If woken, return "ok", otherwise return "timed-out". The initial check and
   // the decision to wait happen atomically.
-  static Object WaitJs32(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
-                         size_t addr, int32_t value, double rel_timeout_ms);
+  static Object WaitJs32(Isolate* isolate, WaitMode mode,
+                         Handle<JSArrayBuffer> array_buffer, size_t addr,
+                         int32_t value, double rel_timeout_ms);
 
   // An version of WaitJs32 for int64_t values.
-  static Object WaitJs64(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
-                         size_t addr, int64_t value, double rel_timeout_ms);
+  static Object WaitJs64(Isolate* isolate, WaitMode mode,
+                         Handle<JSArrayBuffer> array_buffer, size_t addr,
+                         int64_t value, double rel_timeout_ms);
 
   // Same as WaitJs above except it returns 0 (ok), 1 (not equal) and 2 (timed
   // out) as expected by Wasm.
@@ -146,23 +201,64 @@
                                        size_t addr,
                                        uint32_t num_waiters_to_wake);
 
-  // Return the number of threads waiting on |addr|. Should only be used for
-  // testing.
+  // Called before |isolate| dies. Removes async waiters owned by |isolate|.
+  static void IsolateDeinit(Isolate* isolate);
+
+  // Return the number of threads or async waiters waiting on |addr|. Should
+  // only be used for testing.
   static Object NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
                                      size_t addr);
 
+  // Return the number of async waiters (which belong to |isolate|) waiting.
+  // Should only be used for testing.
+  static Object NumAsyncWaitersForTesting(Isolate* isolate);
+
+  // Return the number of async waiters which were waiting for |addr| and are
+  // now waiting for the Promises to be resolved. Should only be used for
+  // testing.
+  static Object NumUnresolvedAsyncPromisesForTesting(
+      Handle<JSArrayBuffer> array_buffer, size_t addr);
+
  private:
   friend class FutexWaitListNode;
   friend class AtomicsWaitWakeHandle;
+  friend class ResolveAsyncWaiterPromisesTask;
+  friend class AsyncWaiterTimeoutTask;
 
   template <typename T>
-  static Object Wait(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
-                     size_t addr, T value, double rel_timeout_ms);
+  static Object Wait(Isolate* isolate, WaitMode mode,
+                     Handle<JSArrayBuffer> array_buffer, size_t addr, T value,
+                     double rel_timeout_ms);
 
   template <typename T>
-  static Object Wait(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
-                     size_t addr, T value, bool use_timeout,
-                     int64_t rel_timeout_ns);
+  static Object Wait(Isolate* isolate, WaitMode mode,
+                     Handle<JSArrayBuffer> array_buffer, size_t addr, T value,
+                     bool use_timeout, int64_t rel_timeout_ns);
+
+  template <typename T>
+  static Object WaitSync(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
+                         size_t addr, T value, bool use_timeout,
+                         int64_t rel_timeout_ns);
+
+  template <typename T>
+  static Object WaitAsync(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
+                          size_t addr, T value, bool use_timeout,
+                          int64_t rel_timeout_ns);
+
+  // Resolve the Promises of the async waiters which belong to |isolate|.
+  static void ResolveAsyncWaiterPromises(Isolate* isolate);
+
+  static void ResolveAsyncWaiterPromise(FutexWaitListNode* node);
+
+  static void HandleAsyncWaiterTimeout(FutexWaitListNode* node);
+
+  static void NotifyAsyncWaiter(FutexWaitListNode* node);
+
+  // Remove the node's Promise from the NativeContext's Promise set.
+  static void CleanupAsyncWaiterPromise(FutexWaitListNode* node);
+
+  // Deletes |node| and returns the next node of its list.
+  static FutexWaitListNode* DeleteAsyncWaiterNode(FutexWaitListNode* node);
 
   // `mutex_` protects the composition of `wait_list_` (i.e. no elements may be
   // added or removed without holding this mutex), as well as the `waiting_`
diff --git a/src/execution/isolate.cc b/src/execution/isolate.cc
index 7b0beeb3..f157fcd 100644
--- a/src/execution/isolate.cc
+++ b/src/execution/isolate.cc
@@ -2943,6 +2943,8 @@
   }
 #endif  // V8_OS_WIN64
 
+  FutexEmulation::IsolateDeinit(this);
+
   debug()->Unload();
 
   wasm_engine()->DeleteCompileJobsOnIsolate(this);
diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h
index 916a3ec..bd610d0 100644
--- a/src/flags/flag-definitions.h
+++ b/src/flags/flag-definitions.h
@@ -223,7 +223,8 @@
   V(harmony_regexp_sequence, "RegExp Unicode sequence properties")             \
   V(harmony_weak_refs_with_cleanup_some,                                       \
     "harmony weak references with FinalizationRegistry.prototype.cleanupSome") \
-  V(harmony_regexp_match_indices, "harmony regexp match indices")
+  V(harmony_regexp_match_indices, "harmony regexp match indices")              \
+  V(harmony_atomics_waitasync, "harmony Atomics.waitAsync")
 
 #ifdef V8_INTL_SUPPORT
 #define HARMONY_INPROGRESS(V)                       \
diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc
index 030a487..13c1438 100644
--- a/src/init/bootstrapper.cc
+++ b/src/init/bootstrapper.cc
@@ -55,6 +55,7 @@
 #include "src/objects/js-segmenter.h"
 #endif  // V8_INTL_SUPPORT
 #include "src/objects/js-weak-refs.h"
+#include "src/objects/ordered-hash-table.h"
 #include "src/objects/property-cell.h"
 #include "src/objects/slots-inl.h"
 #include "src/objects/templates.h"
@@ -4129,6 +4130,12 @@
 
 #undef EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE
 
+void Genesis::InitializeGlobal_harmony_atomics_waitasync() {
+  if (!FLAG_harmony_atomics_waitasync) return;
+  SimpleInstallFunction(isolate(), isolate()->atomics_object(), "waitAsync",
+                        Builtins::kAtomicsWaitAsync, 4, true);
+}
+
 void Genesis::InitializeGlobal_harmony_sharedarraybuffer() {
   if (!FLAG_harmony_sharedarraybuffer) return;
 
@@ -4736,6 +4743,11 @@
       map->AppendDescriptor(isolate(), &d);
     }
   }
+  {
+    Handle<OrderedHashSet> promises =
+        OrderedHashSet::Allocate(isolate(), 0).ToHandleChecked();
+    native_context()->set_atomics_waitasync_promises(*promises);
+  }
 
   return true;
 }
diff --git a/src/objects/contexts-inl.h b/src/objects/contexts-inl.h
index 8bd4172..be5cddc 100644
--- a/src/objects/contexts-inl.h
+++ b/src/objects/contexts-inl.h
@@ -5,14 +5,14 @@
 #ifndef V8_OBJECTS_CONTEXTS_INL_H_
 #define V8_OBJECTS_CONTEXTS_INL_H_
 
-#include "src/objects/contexts.h"
-
 #include "src/heap/heap-write-barrier.h"
+#include "src/objects/contexts.h"
 #include "src/objects/dictionary-inl.h"
 #include "src/objects/fixed-array-inl.h"
 #include "src/objects/js-objects-inl.h"
 #include "src/objects/map-inl.h"
 #include "src/objects/objects-inl.h"
+#include "src/objects/ordered-hash-table-inl.h"
 #include "src/objects/osr-optimized-code-cache-inl.h"
 #include "src/objects/regexp-match-info.h"
 #include "src/objects/scope-info.h"
diff --git a/src/objects/contexts.h b/src/objects/contexts.h
index b7f0570..4969ff0 100644
--- a/src/objects/contexts.h
+++ b/src/objects/contexts.h
@@ -7,6 +7,7 @@
 
 #include "src/objects/fixed-array.h"
 #include "src/objects/function-kind.h"
+#include "src/objects/ordered-hash-table.h"
 #include "src/objects/osr-optimized-code-cache.h"
 #include "torque-generated/field-offsets-tq.h"
 // Has to be the last include (doesn't have include guards):
@@ -231,6 +232,7 @@
     slow_object_with_object_prototype_map)                                     \
   V(SLOW_TEMPLATE_INSTANTIATIONS_CACHE_INDEX, SimpleNumberDictionary,          \
     slow_template_instantiations_cache)                                        \
+  V(ATOMICS_WAITASYNC_PROMISES, OrderedHashSet, atomics_waitasync_promises)    \
   /* Fast Path Protectors */                                                   \
   V(REGEXP_SPECIES_PROTECTOR_INDEX, PropertyCell, regexp_species_protector)    \
   /* All *_FUNCTION_MAP_INDEX definitions used by Context::FunctionMapIndex */ \
diff --git a/src/runtime/runtime-futex.cc b/src/runtime/runtime-futex.cc
index c251653..cb7facf 100644
--- a/src/runtime/runtime-futex.cc
+++ b/src/runtime/runtime-futex.cc
@@ -36,6 +36,28 @@
   return FutexEmulation::NumWaitersForTesting(array_buffer, addr);
 }
 
+RUNTIME_FUNCTION(Runtime_AtomicsNumAsyncWaitersForTesting) {
+  DCHECK_EQ(0, args.length());
+  return FutexEmulation::NumAsyncWaitersForTesting(isolate);
+}
+
+RUNTIME_FUNCTION(Runtime_AtomicsNumUnresolvedAsyncPromisesForTesting) {
+  HandleScope scope(isolate);
+  DCHECK_EQ(2, args.length());
+  CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, sta, 0);
+  CONVERT_SIZE_ARG_CHECKED(index, 1);
+  CHECK(!sta->WasDetached());
+  CHECK(sta->GetBuffer()->is_shared());
+  CHECK_LT(index, sta->length());
+  CHECK_EQ(sta->type(), kExternalInt32Array);
+
+  Handle<JSArrayBuffer> array_buffer = sta->GetBuffer();
+  size_t addr = (index << 2) + sta->byte_offset();
+
+  return FutexEmulation::NumUnresolvedAsyncPromisesForTesting(array_buffer,
+                                                              addr);
+}
+
 RUNTIME_FUNCTION(Runtime_SetAllowAtomicsWait) {
   HandleScope scope(isolate);
   DCHECK_EQ(1, args.length());
diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h
index f9ee395..1d0b80c 100644
--- a/src/runtime/runtime.h
+++ b/src/runtime/runtime.h
@@ -51,17 +51,19 @@
   F(TransitionElementsKind, 2, 1)      \
   F(TransitionElementsKindWithKind, 2, 1)
 
-#define FOR_EACH_INTRINSIC_ATOMICS(F, I) \
-  F(AtomicsLoad64, 2, 1)                 \
-  F(AtomicsStore64, 3, 1)                \
-  F(AtomicsAdd, 3, 1)                    \
-  F(AtomicsAnd, 3, 1)                    \
-  F(AtomicsCompareExchange, 4, 1)        \
-  F(AtomicsExchange, 3, 1)               \
-  F(AtomicsNumWaitersForTesting, 2, 1)   \
-  F(AtomicsOr, 3, 1)                     \
-  F(AtomicsSub, 3, 1)                    \
-  F(AtomicsXor, 3, 1)                    \
+#define FOR_EACH_INTRINSIC_ATOMICS(F, I)               \
+  F(AtomicsLoad64, 2, 1)                               \
+  F(AtomicsStore64, 3, 1)                              \
+  F(AtomicsAdd, 3, 1)                                  \
+  F(AtomicsAnd, 3, 1)                                  \
+  F(AtomicsCompareExchange, 4, 1)                      \
+  F(AtomicsExchange, 3, 1)                             \
+  F(AtomicsNumWaitersForTesting, 2, 1)                 \
+  F(AtomicsNumAsyncWaitersForTesting, 0, 1)            \
+  F(AtomicsNumUnresolvedAsyncPromisesForTesting, 2, 1) \
+  F(AtomicsOr, 3, 1)                                   \
+  F(AtomicsSub, 3, 1)                                  \
+  F(AtomicsXor, 3, 1)                                  \
   F(SetAllowAtomicsWait, 1, 1)
 
 #define FOR_EACH_INTRINSIC_BIGINT(F, I) \
diff --git a/src/tasks/cancelable-task.h b/src/tasks/cancelable-task.h
index 59f04de..9592902 100644
--- a/src/tasks/cancelable-task.h
+++ b/src/tasks/cancelable-task.h
@@ -32,6 +32,7 @@
 class V8_EXPORT_PRIVATE CancelableTaskManager {
  public:
   using Id = uint64_t;
+  static constexpr Id kInvalidTaskId = 0;
 
   CancelableTaskManager();
 
@@ -68,8 +69,6 @@
   bool canceled() const { return canceled_; }
 
  private:
-  static constexpr Id kInvalidTaskId = 0;
-
   // Only called by {Cancelable} destructor. The task is done with executing,
   // but needs to be removed.
   void RemoveFinishedTask(Id id);
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-2timeout.js b/test/mjsunit/harmony/atomics-waitasync-1thread-2timeout.js
new file mode 100644
index 0000000..688840c
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-2timeout.js
@@ -0,0 +1,50 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  // Create a waiter with a long timeout.
+  const result_slow = Atomics.waitAsync(i32a, 0, 0, 200000);
+  // Create a waiter with a short timeout.
+  const result_fast = Atomics.waitAsync(i32a, 0, 0, 1);
+
+  assertEquals(true, result_slow.async);
+  assertEquals(true, result_fast.async);
+  assertEquals(2, %AtomicsNumWaitersForTesting(i32a, 0));
+
+  let slow_value = undefined;
+  result_slow.value.then(
+   (value) => { slow_value = value; },
+   () => { assertUnreachable(); });
+
+  let fast_value = undefined;
+  result_fast.value.then(
+   (value) => { fast_value = value; },
+   () => { assertUnreachable(); });
+
+  // Verify that the waiter with the short time out times out.
+  let rounds = 1000;
+  function continuation1() {
+    --rounds;
+    assertTrue(rounds > 0);
+    if (fast_value == undefined) {
+      setTimeout(continuation1, 0);
+    } else {
+      assertEquals("timed-out", fast_value);
+      // Wake up the waiter with the long time out.
+      let notify_return_value = Atomics.notify(i32a, 0, 1);
+      assertEquals(1, notify_return_value);
+      setTimeout(continuation2, 0);
+    }
+  }
+  function continuation2() {
+    assertEquals("ok", slow_value);
+  }
+
+  setTimeout(continuation1, 0);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-buffer-out-of-scope-timeout.js b/test/mjsunit/harmony/atomics-waitasync-1thread-buffer-out-of-scope-timeout.js
new file mode 100644
index 0000000..a3d35af
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-buffer-out-of-scope-timeout.js
@@ -0,0 +1,48 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync --expose-gc --no-stress-opt
+
+(function test() {
+  let timed_out = false;
+
+  (function() {
+    const sab = new SharedArrayBuffer(16);
+    const i32a = new Int32Array(sab);
+    // Create a waiter with a timeout.
+    const result = Atomics.waitAsync(i32a, 0, 0, 1);
+    result.value.then(
+      (value) => { assertEquals('timed-out', value); timed_out = true; },
+      () => { assertUnreachable(); });
+    })();
+  // Make sure sab, ia32 and result get gc()d.
+  gc();
+
+  assertEquals(1, %AtomicsNumAsyncWaitersForTesting());
+
+  // Even if the buffer went out of scope, we keep the waitAsync alive so that it can still time out.
+  let resolved = false;
+  const sab2 = new SharedArrayBuffer(16);
+  const i32a2 = new Int32Array(sab2);
+  const result2 = Atomics.waitAsync(i32a2, 0, 0);
+  result2.value.then(
+    (value) => { assertEquals("ok", value); resolved = true; },
+    () => { assertUnreachable(); });
+  assertEquals(2, %AtomicsNumAsyncWaitersForTesting());
+
+  const notify_return_value = Atomics.notify(i32a2, 0);
+  assertEquals(1, notify_return_value);
+  assertEquals(1, %AtomicsNumAsyncWaitersForTesting());
+
+  // Verify that the waiter gets woken up.
+  let rounds = 1000;
+  function wait() {
+    --rounds;
+    assertTrue(rounds > 0);
+    if (!timed_out) {
+      setTimeout(wait, 0);
+    }
+  }
+  setTimeout(wait, 0);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-buffer-out-of-scope.js b/test/mjsunit/harmony/atomics-waitasync-1thread-buffer-out-of-scope.js
new file mode 100644
index 0000000..bf11872
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-buffer-out-of-scope.js
@@ -0,0 +1,39 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync --expose-gc --no-stress-opt
+
+(function test() {
+  (function() {
+    const sab = new SharedArrayBuffer(16);
+    const i32a = new Int32Array(sab);
+    const result = Atomics.waitAsync(i32a, 0, 0);
+    result.value.then(
+      (value) => { assertUnreachable(); },
+      () => { assertUnreachable(); });
+    })();
+  // Make sure sab, ia32 and result get gc()d.
+  gc();
+
+  assertEquals(1, %AtomicsNumAsyncWaitersForTesting());
+
+  // The next time iterate the waiter list, we clean up the waiter which can
+  // never be woken up.
+  let resolved = false;
+  const sab2 = new SharedArrayBuffer(16);
+  const i32a2 = new Int32Array(sab2);
+  const result2 = Atomics.waitAsync(i32a2, 0, 0);
+  result2.value.then(
+    (value) => { assertEquals("ok", value); resolved = true; },
+    () => { assertUnreachable(); });
+  assertEquals(2, %AtomicsNumAsyncWaitersForTesting());
+
+  const notify_return_value = Atomics.notify(i32a2, 0);
+  assertEquals(1, notify_return_value);
+  assertEquals(0, %AtomicsNumAsyncWaitersForTesting());
+
+  setTimeout(()=> {
+    assertTrue(resolved);
+  }, 0);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-promise-out-of-scope.js b/test/mjsunit/harmony/atomics-waitasync-1thread-promise-out-of-scope.js
new file mode 100644
index 0000000..09decbf
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-promise-out-of-scope.js
@@ -0,0 +1,30 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync --expose-gc
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  let resolved = false;
+  (function() {
+    const result = Atomics.waitAsync(i32a, 0, 0);
+    result.value.then(
+      (value) => { assertEquals("ok", value); resolved = true; },
+      () => { assertUnreachable(); });
+    })();
+  // Make sure result gets gc()d.
+  gc();
+
+  const notify_return_value = Atomics.notify(i32a, 0, 1);
+  assertEquals(1, notify_return_value);
+  assertEquals(0, %AtomicsNumWaitersForTesting(i32a, 0));
+  assertEquals(1, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+  setTimeout(()=> {
+    assertTrue(resolved);
+    assertEquals(0, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+  }, 0);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-timeout.js b/test/mjsunit/harmony/atomics-waitasync-1thread-timeout.js
new file mode 100644
index 0000000..2418aca
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-timeout.js
@@ -0,0 +1,32 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  // Create a waiter with a timeout.
+  const result = Atomics.waitAsync(i32a, 0, 0, 1);
+
+  assertEquals(true, result.async);
+  assertEquals(1, %AtomicsNumWaitersForTesting(i32a, 0));
+
+  let resolved = false;
+  result.value.then(
+   (value) => { assertEquals("timed-out", value); resolved = true; },
+   () => { assertUnreachable(); });
+
+  // Verify that the waiter gets woken up.
+  let rounds = 1000;
+  function wait() {
+    --rounds;
+    assertTrue(rounds > 0);
+    if (!resolved) {
+      setTimeout(wait, 0);
+    }
+  }
+  setTimeout(wait, 0);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-timeouts-and-no-timeouts.js b/test/mjsunit/harmony/atomics-waitasync-1thread-timeouts-and-no-timeouts.js
new file mode 100644
index 0000000..8c34e19
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-timeouts-and-no-timeouts.js
@@ -0,0 +1,75 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  const N = 10;
+  let log = [];
+
+  // Create N async waiters; the even ones without timeout and the odd ones
+  // with timeout.
+  for (let i = 0; i < N; ++i) {
+    let result;
+    if (i % 2 == 0) {
+      result = Atomics.waitAsync(i32a, 0, 0);
+    } else {
+      result = Atomics.waitAsync(i32a, 0, 0, i);
+    }
+    assertEquals(true, result.async);
+    result.value.then(
+      (value) => { log.push(value + " " + i); },
+      () => { assertUnreachable(); });
+  }
+  assertEquals(N, %AtomicsNumWaitersForTesting(i32a, 0));
+  assertEquals(0, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+  // Wait until the timed out waiters time out.
+  let rounds = 10000;
+  let previous_length = 0;
+  function wait() {
+    --rounds;
+    assertTrue(rounds > 0);
+    if (log.length > previous_length) {
+        // Made progress. Give the test more time.
+        previous_length = log.length;
+        rounds = 10000;
+    }
+    if (log.length < N / 2) {
+      setTimeout(wait, 0);
+    } else {
+      continuation1();
+    }
+  }
+  setTimeout(wait, 0);
+
+  function continuation1() {
+    // Verify that all timed out waiters timed out in FIFO order.
+    assertEquals(N / 2, log.length);
+    let waiter_no = 1;
+    for (let i = 0; i < N / 2; ++i) {
+      assertEquals("timed-out " + waiter_no, log[i]);
+      waiter_no += 2;
+    }
+    // Wake up all waiters
+    let notify_return_value = Atomics.notify(i32a, 0);
+    assertEquals(N / 2, notify_return_value);
+    assertEquals(0, %AtomicsNumWaitersForTesting(i32a, 0));
+    assertEquals(N / 2, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+    setTimeout(continuation2, 0);
+  }
+
+  function continuation2() {
+    // Verify that the waiters woke up in FIFO order.
+    assertEquals(N, log.length);
+    let waiter_no = 0;
+    for (let i = N / 2; i < N; ++i) {
+      assertEquals("ok " + waiter_no, log[i]);
+      waiter_no += 2;
+    }
+  }
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-all.js b/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-all.js
new file mode 100644
index 0000000..d0bc645
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-all.js
@@ -0,0 +1,39 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  const N = 10;
+  let log = [];
+
+  // Create N async waiters.
+  for (let i = 0; i < N; ++i) {
+    const result = Atomics.waitAsync(i32a, 0, 0);
+    assertEquals(true, result.async);
+    result.value.then(
+      (value) => { assertEquals("ok", value); log.push(i); },
+      () => { assertUnreachable(); });
+  }
+  assertEquals(N, %AtomicsNumWaitersForTesting(i32a, 0));
+  assertEquals(0, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+  // Wake up all waiters.
+  let notify_return_value = Atomics.notify(i32a, 0);
+  assertEquals(N, notify_return_value);
+  assertEquals(0, %AtomicsNumWaitersForTesting(i32a, 0));
+  assertEquals(N, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+  function continuation() {
+    assertEquals(N, log.length);
+    for (let i = 0; i < N; ++i) {
+        assertEquals(i, log[i]);
+    }
+  }
+
+  setTimeout(continuation, 0);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-fifo.js b/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-fifo.js
new file mode 100644
index 0000000..e29c4c3
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-fifo.js
@@ -0,0 +1,55 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  // Create 2 async waiters.
+  const result1 = Atomics.waitAsync(i32a, 0, 0);
+  const result2 = Atomics.waitAsync(i32a, 0, 0);
+
+  assertEquals(true, result1.async);
+  assertEquals(true, result2.async);
+  assertEquals(2, %AtomicsNumWaitersForTesting(i32a, 0));
+  assertEquals(0, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+  let log = [];
+  result1.value.then(
+   (value) => { assertEquals("ok", value); log.push(1); },
+   () => { assertUnreachable(); });
+  result2.value.then(
+    (value) => { assertEquals("ok", value); log.push(2); },
+    () => { assertUnreachable(); });
+
+  // Wake up one waiter.
+  const notify_return_value = Atomics.notify(i32a, 0, 1);
+  assertEquals(1, notify_return_value);
+  assertEquals(1, %AtomicsNumWaitersForTesting(i32a, 0));
+  assertEquals(1, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+  function continuation1() {
+    assertEquals(1, %AtomicsNumWaitersForTesting(i32a, 0));
+    assertEquals(0, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+    assertEquals([1], log);
+
+    // Wake up one waiter.
+    const notify_return_value = Atomics.notify(i32a, 0, 1);
+    assertEquals(1, notify_return_value);
+    assertEquals(0, %AtomicsNumWaitersForTesting(i32a, 0));
+    assertEquals(1, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+    setTimeout(continuation2, 0);
+  }
+
+  function continuation2() {
+    assertEquals(0, %AtomicsNumWaitersForTesting(i32a, 0));
+    assertEquals(0, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+    assertEquals([1, 2], log);
+  }
+
+  setTimeout(continuation1, 0);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-simple.js b/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-simple.js
new file mode 100644
index 0000000..b443f86
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-1thread-wake-up-simple.js
@@ -0,0 +1,30 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  const result = Atomics.waitAsync(i32a, 0, 0);
+  assertEquals(true, result.async);
+  assertTrue(result.value instanceof Promise);
+  assertEquals(1, %AtomicsNumWaitersForTesting(i32a, 0));
+  assertEquals(0, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+  let resolved = false;
+   result.value.then(
+    (value) => { assertEquals("ok", value); resolved = true; },
+    () => { assertUnreachable(); });
+  const notify_return_value = Atomics.notify(i32a, 0, 1);
+  assertEquals(1, notify_return_value);
+  assertEquals(0, %AtomicsNumWaitersForTesting(i32a, 0));
+  assertEquals(1, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+
+  setTimeout(()=> {
+    assertTrue(resolved);
+    assertEquals(0, %AtomicsNumUnresolvedAsyncPromisesForTesting(i32a, 0));
+  }, 0);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-worker-shutdown-before-wait-finished-no-timeout.js b/test/mjsunit/harmony/atomics-waitasync-worker-shutdown-before-wait-finished-no-timeout.js
new file mode 100644
index 0000000..e698a6e
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-worker-shutdown-before-wait-finished-no-timeout.js
@@ -0,0 +1,29 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync --expose-gc
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  (function createWorker() {
+    const script = `onmessage = function(msg) {
+    if (msg.sab) {
+        const i32a = new Int32Array(msg.sab);
+        const result = Atomics.waitAsync(i32a, 0, 0);
+        postMessage('worker waiting');
+      }
+    }`;
+    const w = new Worker(script, {type : 'string'});
+    w.postMessage({sab: sab});
+    const m = w.getMessage();
+    assertEquals('worker waiting', m);
+    w.terminate();
+  })();
+
+  gc();
+
+  Atomics.notify(i32a, 0, 1);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync-worker-shutdown-before-wait-finished-timeout.js b/test/mjsunit/harmony/atomics-waitasync-worker-shutdown-before-wait-finished-timeout.js
new file mode 100644
index 0000000..6db3ec7
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync-worker-shutdown-before-wait-finished-timeout.js
@@ -0,0 +1,29 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync --expose-gc
+
+(function test() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  (function createWorker() {
+    const script = `onmessage = function(msg) {
+    if (msg.sab) {
+        const i32a = new Int32Array(msg.sab);
+        const result = Atomics.waitAsync(i32a, 0, 0, 100000);
+        postMessage('worker waiting');
+      }
+    }`;
+    const w = new Worker(script, {type : 'string'});
+    w.postMessage({sab: sab});
+    const m = w.getMessage();
+    assertEquals('worker waiting', m);
+    w.terminate();
+  })();
+
+  gc();
+
+  Atomics.notify(i32a, 0, 1);
+})();
diff --git a/test/mjsunit/harmony/atomics-waitasync.js b/test/mjsunit/harmony/atomics-waitasync.js
new file mode 100644
index 0000000..d5165b9
--- /dev/null
+++ b/test/mjsunit/harmony/atomics-waitasync.js
@@ -0,0 +1,31 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax --harmony-sharedarraybuffer --harmony-atomics-waitasync
+
+(function testOutOfBounds() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+  assertThrows(() => {
+    Atomics.waitAsync(i32a, 20, 0, 1000);
+  }, RangeError);
+})();
+
+(function testValueNotEquals() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  const result = Atomics.waitAsync(i32a, 0, 1, 1000);
+  assertEquals(false, result.async);
+  assertEquals("not-equal", result.value);
+})();
+
+(function testZeroTimeout() {
+  const sab = new SharedArrayBuffer(16);
+  const i32a = new Int32Array(sab);
+
+  const result = Atomics.waitAsync(i32a, 0, 0, 0);
+  assertEquals(false, result.async);
+  assertEquals("timed-out", result.value);
+})();
diff --git a/test/mjsunit/mjsunit.status b/test/mjsunit/mjsunit.status
index bb7f43f..3544396 100644
--- a/test/mjsunit/mjsunit.status
+++ b/test/mjsunit/mjsunit.status
@@ -842,6 +842,17 @@
   # Tier down/up Wasm NativeModule in debugging is non-deterministic with
   # multiple isolates (https://crbug.com/v8/10099).
   'wasm/tier-down-to-liftoff': [SKIP],
+
+  # waitAsync tests modify the global state (across Isolates)
+  'harmony/atomics-waitasync': [SKIP],
+  'harmony/atomics-waitasync-1thread-2timeout': [SKIP],
+  'harmony/atomics-waitasync-1thread-buffer-out-of-scope': [SKIP],
+  'harmony/atomics-waitasync-1thread-promise-out-of-scope': [SKIP],
+  'harmony/atomics-waitasync-1thread-timeout': [SKIP],
+  'harmony/atomics-waitasync-1thread-wake-up-fifo': [SKIP],
+  'harmony/atomics-waitasync-1thread-wake-up-simple': [SKIP],
+  'harmony/atomics-waitasync-worker-shutdown-before-wait-finished-timeout': [SKIP],
+  'harmony/atomics-waitasync-worker-shutdown-before-wait-finished-no-timeout': [SKIP],
 }],  # 'isolates'
 
 ##############################################################################
diff --git a/test/test262/test262.status b/test/test262/test262.status
index 2fe783c..f911ca4 100644
--- a/test/test262/test262.status
+++ b/test/test262/test262.status
@@ -533,109 +533,6 @@
   # https://bugs.chromium.org/p/v8/issues/detail?id=10383
   'built-ins/RegExp/prototype/Symbol.replace/fn-invoke-args-empty-result': [FAIL],
 
-  # http://crbug/v8/10239
-  'built-ins/Atomics/waitAsync/bad-range': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/bad-range': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/false-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/false-for-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/good-views': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/nan-for-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/negative-index-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/negative-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/negative-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-no-operation': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-on-add': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-on-and': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-on-compareExchange': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-on-exchange': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-on-or': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-on-store': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-on-sub': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/no-spurious-wakeup-on-xor': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/non-bigint64-typedarray-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/non-shared-bufferdata-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/not-a-typedarray-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/not-an-object-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/null-bufferdata-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/null-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/null-for-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/object-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/object-for-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/out-of-range-index-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/poisoned-object-for-timeout-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/poisoned-object-for-timeout-throws-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/symbol-for-index-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/symbol-for-index-throws-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/symbol-for-timeout-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/symbol-for-timeout-throws-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/symbol-for-value-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/symbol-for-value-throws-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/true-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/true-for-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/undefined-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/undefined-for-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/undefined-index-defaults-to-zero-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/value-not-equal': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/value-not-equal-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/waiterlist-block-indexedposition-wake': [FAIL],
-  'built-ins/Atomics/waitAsync/bigint/was-woken-before-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/descriptor': [FAIL],
-  'built-ins/Atomics/waitAsync/false-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/is-function': [FAIL],
-  'built-ins/Atomics/waitAsync/length': [FAIL],
-  'built-ins/Atomics/waitAsync/name': [FAIL],
-  'built-ins/Atomics/waitAsync/nan-for-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/negative-index-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/negative-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/non-int32-typedarray-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/non-shared-bufferdata-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/not-a-typedarray-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/not-an-object-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/null-bufferdata-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/null-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/object-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/out-of-range-index-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/poisoned-object-for-timeout-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/poisoned-object-for-timeout-throws-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/returns-result-object-value-is-promise-resolves-to-ok': [FAIL],
-  'built-ins/Atomics/waitAsync/returns-result-object-value-is-promise-resolves-to-timed-out': [FAIL],
-  'built-ins/Atomics/waitAsync/returns-result-object-value-is-string-not-equal': [FAIL],
-  'built-ins/Atomics/waitAsync/returns-result-object-value-is-string-timed-out': [FAIL],
-  'built-ins/Atomics/waitAsync/symbol-for-index-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/symbol-for-index-throws-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/symbol-for-timeout-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/symbol-for-timeout-throws-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/symbol-for-value-throws': [FAIL],
-  'built-ins/Atomics/waitAsync/symbol-for-value-throws-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/true-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/undefined-for-timeout-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/undefined-for-timeout': [FAIL],
-  'built-ins/Atomics/waitAsync/undefined-index-defaults-to-zero-agent': [FAIL],
-  'built-ins/Atomics/waitAsync/validate-arraytype-before-index-coercion': [FAIL],
-  'built-ins/Atomics/waitAsync/validate-arraytype-before-timeout-coercion': [FAIL],
-  'built-ins/Atomics/waitAsync/validate-arraytype-before-value-coercion': [FAIL],
-  'built-ins/Atomics/waitAsync/value-not-equal': [FAIL],
-  'built-ins/Atomics/waitAsync/waiterlist-block-indexedposition-wake': [FAIL],
-  'built-ins/Atomics/waitAsync/was-woken-before-timeout': [FAIL],
-
-  # SKIP the following TIMEOUT tests instead of FAIL
-  'built-ins/Atomics/waitAsync/false-for-timeout-agent': [SKIP],
-  'built-ins/Atomics/waitAsync/good-views': [SKIP],
-  'built-ins/Atomics/waitAsync/negative-timeout-agent': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-no-operation': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-on-add': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-on-and': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-on-compareExchange': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-on-exchange': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-on-or': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-on-store': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-on-sub': [SKIP],
-  'built-ins/Atomics/waitAsync/no-spurious-wakeup-on-xor': [SKIP],
-  'built-ins/Atomics/waitAsync/null-for-timeout-agent': [SKIP],
-  'built-ins/Atomics/waitAsync/object-for-timeout-agent': [SKIP],
-  'built-ins/Atomics/waitAsync/true-for-timeout-agent': [SKIP],
-  'built-ins/Atomics/waitAsync/value-not-equal-agent': [SKIP],
-
   # https://crbug.com/v8/10687
   'built-ins/Atomics/add/bigint/non-shared-bufferdata': [FAIL],
   'built-ins/Atomics/add/non-shared-bufferdata': [FAIL],
diff --git a/test/test262/testcfg.py b/test/test262/testcfg.py
index 563bfbb..8944665 100644
--- a/test/test262/testcfg.py
+++ b/test/test262/testcfg.py
@@ -65,6 +65,7 @@
   'AggregateError': '--harmony-promise-any',
   'logical-assignment-operators': '--harmony-logical-assignment',
   'Promise.any': '--harmony-promise-any',
+  'Atomics.waitAsync': '--harmony-atomics-waitasync',
 }
 
 SKIPPED_FEATURES = set([])