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: Andreas Haas <ahaas@chromium.org> Reviewed-by: Shu-yu Guo <syg@chromium.org> Cr-Commit-Position: refs/heads/master@{#68954}
This commit is contained in:
parent
9e87de26f6
commit
96c5916dd4
@ -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, 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_`
|
||||
|
@ -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);
|
||||
|
39
test/mjsunit/harmony/atomics-waitasync-1thread-2timeout.js
Normal file
39
test/mjsunit/harmony/atomics-waitasync-1thread-2timeout.js
Normal file
@ -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: --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);
|
@ -0,0 +1,42 @@
|
||||
// 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);
|
@ -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);
|
||||
})();
|
26
test/mjsunit/harmony/atomics-waitasync-1thread-timeout.js
Normal file
26
test/mjsunit/harmony/atomics-waitasync-1thread-timeout.js
Normal file
@ -0,0 +1,26 @@
|
||||
// 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);
|
@ -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;
|
||||
}
|
||||
}
|
||||
})();
|
@ -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);
|
||||
})();
|
@ -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);
|
||||
})();
|
@ -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);
|
||||
})();
|
15
test/mjsunit/harmony/atomics-waitasync-helpers.js
Normal file
15
test/mjsunit/harmony/atomics-waitasync-helpers.js
Normal file
@ -0,0 +1,15 @@
|
||||
// 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();
|
||||
}
|
@ -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);
|
||||
})();
|
@ -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);
|
||||
})();
|
31
test/mjsunit/harmony/atomics-waitasync.js
Normal file
31
test/mjsunit/harmony/atomics-waitasync.js
Normal file
@ -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);
|
||||
})();
|
@ -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([])
|
||||
|
Loading…
Reference in New Issue
Block a user