Commit 96c5916d authored by Marja Hölttä's avatar Marja Hölttä Committed by Commit Bot

Reland2: [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

Reland:
- rewrote timing dependent tests to be more robust
- removed 1 flaky test
- disabled tests for DelayedTasksPlatform

Original:  https://chromium-review.googlesource.com/c/v8/v8/+/2202981

TBR=ishell@chromium.org, ulan@chromium.org

Bug: v8:10239
Change-Id: I2a042e419462f4c9f54ec549bfe16ec6684560b7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2307211
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68954}
parent 9e87de26
......@@ -745,6 +745,7 @@ namespace internal {
CPP(AtomicsNotify) \
CPP(AtomicsIsLockFree) \
CPP(AtomicsWait) \
CPP(AtomicsWaitAsync) \
CPP(AtomicsWake) \
\
/* String */ \
......
......@@ -177,25 +177,22 @@ BUILTIN(AtomicsNotify) {
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 @@ BUILTIN(AtomicsWait) {
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 @@ BUILTIN(AtomicsWait) {
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 @@ BUILTIN(AtomicsWait) {
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
......@@ -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::LazyMutex FutexEmulation::mutex_ = LAZY_MUTEX_INITIALIZER;
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 @@ void FutexWaitListNode::NotifyWake() {
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 @@ void FutexWaitList::AddNode(FutexWaitListNode* node) {
}
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 @@ Object WaitJsTranslateReturn(Isolate* isolate, Object res) {
} // 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 @@ Object FutexEmulation::WaitWasm32(Isolate* isolate,
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 @@ Object FutexEmulation::Wait(Isolate* isolate,
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 @@ double WaitTimeoutInMs(double timeout_ns) {
} // 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 @@ Object FutexEmulation::Wait(Isolate* isolate,
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 @@ Object FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
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 @@ Object FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
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 @@ Object FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
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
......@@ -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 AtomicsWaitWakeHandle {
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 ResetWaitingOnScopeExit {
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 @@ class FutexEmulation : public AllStatic {
// |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 @@ class FutexEmulation : public AllStatic {
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, WaitMode mode,
Handle<JSArrayBuffer> array_buffer, size_t addr, T value,
double rel_timeout_ms);
template <typename T>
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 Wait(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
size_t addr, T value, double rel_timeout_ms);
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 Wait(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
size_t addr, T value, bool use_timeout,
int64_t rel_timeout_ns);
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_`
......
......@@ -2943,6 +2943,8 @@ void Isolate::Deinit() {
}
#endif // V8_OS_WIN64
FutexEmulation::IsolateDeinit(this);
debug()->Unload();
wasm_engine()->DeleteCompileJobsOnIsolate(this);
......
......@@ -223,7 +223,8 @@ DEFINE_IMPLICATION(harmony_weak_refs_with_cleanup_some, harmony_weak_refs)
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) \
......
......@@ -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"
......@@ -4131,6 +4132,12 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(
#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;
......@@ -4738,6 +4745,11 @@ bool Genesis::InstallABunchOfRandomThings() {
map->AppendDescriptor(isolate(), &d);
}
}
{
Handle<OrderedHashSet> promises =
OrderedHashSet::Allocate(isolate(), 0).ToHandleChecked();
native_context()->set_atomics_waitasync_promises(*promises);
}
return true;
}
......
......@@ -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"
......
......@@ -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):
......@@ -233,6 +234,7 @@ enum ContextLookupFlags {
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 */ \
......
......@@ -36,6 +36,28 @@ RUNTIME_FUNCTION(Runtime_AtomicsNumWaitersForTesting) {
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());
......
......@@ -51,17 +51,19 @@ namespace internal {
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) \
......
......@@ -32,6 +32,7 @@ enum class TryAbortResult { kTaskRemoved, kTaskRunning, kTaskAborted };
class V8_EXPORT_PRIVATE CancelableTaskManager {
public:
using Id = uint64_t;
static constexpr Id kInvalidTaskId = 0;
CancelableTaskManager();
......@@ -68,8 +69,6 @@ class V8_EXPORT_PRIVATE CancelableTaskManager {
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);
......
// 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: --harmony-sharedarraybuffer --harmony-atomics-waitasync
load("test/mjsunit/harmony/atomics-waitasync-helpers.js");
const script = `
const sab = new SharedArrayBuffer(16);
const i32a = new Int32Array(sab);
onmessage = function() {
// 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);
result_slow.value.then(
(value) => { postMessage("slow " + value); },
() => { postMessage("unexpected"); });
result_fast.value.then(
(value) => {
postMessage("fast " + value);
// Wake up the waiter with the long time out.
const notify_return_value = Atomics.notify(i32a, 0, 1);
postMessage("notify return value " + notify_return_value);
},
() => { postMessage("unexpected"); });
}`;
const expected_messages = [
"fast timed-out",
"notify return value 1",
"slow ok"
];
runTestWithWorker(script, expected_messages);
// 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: --harmony-sharedarraybuffer --harmony-atomics-waitasync --expose-gc --no-stress-opt
load("test/mjsunit/harmony/atomics-waitasync-helpers.js");
const script = `
onmessage = function() {
(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) => { postMessage("result " + value); },
() => { postMessage("unexpected"); });
})();
// Make sure sab, ia32 and result get gc()d.
gc();
// 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) => { postMessage("result2 " + value); },
() => { postMessage("unexpected"); });
const notify_return_value = Atomics.notify(i32a2, 0);
postMessage("notify return value " + notify_return_value);
}`;
const expected_messages = [
"notify return value 1",
"result2 ok",
"result timed-out"
];
runTestWithWorker(script, expected_messages);
// 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);
})();
// 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: --harmony-sharedarraybuffer --harmony-atomics-waitasync
load("test/mjsunit/harmony/atomics-waitasync-helpers.js");
const script = `
onmessage = 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) => { postMessage("result " + value); },
() => { postMessage("unexpected"); });
}`;
const expected_messages = [
"result timed-out"
];
runTestWithWorker(script, expected_messages);
// 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;
}
}
})();
// 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);
})();
// 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);
})();
// 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);
})();
// 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.
function runTestWithWorker(script, expected_messages) {
const w = new Worker(script, {type : 'string'});
w.postMessage('start');
let i = 0;
while (i < expected_messages.length) {
const m = w.getMessage();
assertEquals(expected_messages[i], m);
++i;
}
w.terminate();
}
// 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);
})();
// 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);
})();
// 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);
})();
......@@ -846,6 +846,16 @@
# 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-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'
##############################################################################
......@@ -917,6 +927,20 @@
'regress/regress-trap-allocation-memento': [SKIP],
'regress/regress-v8-9267-*': [SKIP],
'shared-function-tier-up-turbo': [SKIP],
# These tests are incompatible with stress_delay_tasks since they
# rely on setTimeout vs tasks working predictably.
'harmony/atomics-waitasync-1thread-2timeout': [SKIP],
'harmony/atomics-waitasync-1thread-buffer-out-of-scope-timeout': [SKIP],
'harmony/atomics-waitasync-1thread-promise-out-of-scope': [SKIP],
'harmony/atomics-waitasync-1thread-timeout': [SKIP],
'harmony/atomics-waitasync-1thread-timeouts-and-no-timeouts': [SKIP],
'harmony/atomics-waitasync-1thread-wake-up-all': [SKIP],
'harmony/atomics-waitasync-1thread-wake-up-fifo': [SKIP],
'harmony/atomics-waitasync-1thread-wake-up-simple': [SKIP],
'harmony/atomics-waitasync': [SKIP],
'harmony/atomics-waitasync-worker-shutdown-before-wait-finished-no-timeout': [SKIP],
'harmony/atomics-waitasync-worker-shutdown-before-wait-finished-timeout': [SKIP],
}], # 'gc_fuzzer'
##############################################################################
......@@ -1400,5 +1424,4 @@
'compiler/serializer-feedback-propagation-2': [SKIP],
'compiler/serializer-transition-propagation': [SKIP],
}], # variant == nci
]
......@@ -536,109 +536,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],
......
......@@ -65,6 +65,7 @@ FEATURE_FLAGS = {
'AggregateError': '--harmony-promise-any',
'logical-assignment-operators': '--harmony-logical-assignment',
'Promise.any': '--harmony-promise-any',
'Atomics.waitAsync': '--harmony-atomics-waitasync',
}
SKIPPED_FEATURES = set([])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment