blob: 2529a128c31a5787cc1ac0ac419dee9112815c94 [file] [log] [blame]
// Copyright 2022 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.
#include "src/builtins/builtins-utils-inl.h"
#include "src/objects/js-atomics-synchronization-inl.h"
#include "src/objects/promise-inl.h"
namespace v8 {
namespace internal {
namespace {
std::optional<base::TimeDelta> GetTimeoutDelta(
DirectHandle<Object> timeout_obj) {
double ms = Object::NumberValue(*timeout_obj);
if (!std::isnan(ms)) {
if (ms < 0) ms = 0;
if (ms <= static_cast<double>(std::numeric_limits<int64_t>::max())) {
return base::TimeDelta::FromMilliseconds(static_cast<int64_t>(ms));
}
}
return std::nullopt;
}
DirectHandle<JSPromise> UnlockAsyncLockedMutexFromPromiseHandler(
Isolate* isolate) {
DirectHandle<Context> context(isolate->context(), isolate);
DirectHandle<Object> mutex(
context->get(JSAtomicsMutex::kMutexAsyncContextSlot), isolate);
DirectHandle<Object> unlock_promise(
context->get(JSAtomicsMutex::kUnlockedPromiseAsyncContextSlot), isolate);
DirectHandle<Object> waiter_wrapper_obj(
context->get(JSAtomicsMutex::kAsyncLockedWaiterAsyncContextSlot),
isolate);
auto js_mutex = Cast<JSAtomicsMutex>(mutex);
auto js_unlock_promise = Cast<JSPromise>(unlock_promise);
auto async_locked_waiter_wrapper = Cast<Foreign>(waiter_wrapper_obj);
js_mutex->UnlockAsyncLockedMutex(isolate, async_locked_waiter_wrapper);
return js_unlock_promise;
}
} // namespace
BUILTIN(AtomicsMutexConstructor) {
DCHECK(v8_flags.harmony_struct);
HandleScope scope(isolate);
return *isolate->factory()->NewJSAtomicsMutex();
}
BUILTIN(AtomicsMutexLock) {
DCHECK(v8_flags.harmony_struct);
constexpr char method_name[] = "Atomics.Mutex.lock";
HandleScope scope(isolate);
DirectHandle<Object> js_mutex_obj = args.atOrUndefined(isolate, 1);
if (!IsJSAtomicsMutex(*js_mutex_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
DirectHandle<JSAtomicsMutex> js_mutex = Cast<JSAtomicsMutex>(js_mutex_obj);
DirectHandle<Object> run_under_lock = args.atOrUndefined(isolate, 2);
if (!IsCallable(*run_under_lock)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNotCallable, run_under_lock));
}
// Like Atomics.wait, synchronous locking may block, and so is disallowed on
// the main thread.
//
// This is not a recursive lock, so also throw if recursively locking.
if (!isolate->allow_atomics_wait() || js_mutex->IsCurrentThreadOwner()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kAtomicsOperationNotAllowed,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
DirectHandle<Object> result;
{
JSAtomicsMutex::LockGuard lock_guard(isolate, js_mutex);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result,
Execution::Call(isolate, run_under_lock,
isolate->factory()->undefined_value(), {}));
}
return *result;
}
BUILTIN(AtomicsMutexTryLock) {
DCHECK(v8_flags.harmony_struct);
constexpr char method_name[] = "Atomics.Mutex.tryLock";
HandleScope scope(isolate);
DirectHandle<Object> js_mutex_obj = args.atOrUndefined(isolate, 1);
if (!IsJSAtomicsMutex(*js_mutex_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
DirectHandle<JSAtomicsMutex> js_mutex = Cast<JSAtomicsMutex>(js_mutex_obj);
DirectHandle<Object> run_under_lock = args.atOrUndefined(isolate, 2);
if (!IsCallable(*run_under_lock)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNotCallable, run_under_lock));
}
DirectHandle<Object> callback_result;
bool success;
{
JSAtomicsMutex::TryLockGuard try_lock_guard(isolate, js_mutex);
if (try_lock_guard.locked()) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, callback_result,
Execution::Call(isolate, run_under_lock,
isolate->factory()->undefined_value(), {}));
success = true;
} else {
callback_result = isolate->factory()->undefined_value();
success = false;
}
}
DirectHandle<JSObject> result =
JSAtomicsMutex::CreateResultObject(isolate, callback_result, success);
return *result;
}
BUILTIN(AtomicsMutexLockWithTimeout) {
DCHECK(v8_flags.harmony_struct);
constexpr char method_name[] = "Atomics.Mutex.lockWithTimeout";
HandleScope scope(isolate);
DirectHandle<Object> js_mutex_obj = args.atOrUndefined(isolate, 1);
if (!IsJSAtomicsMutex(*js_mutex_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
DirectHandle<JSAtomicsMutex> js_mutex = Cast<JSAtomicsMutex>(js_mutex_obj);
DirectHandle<Object> run_under_lock = args.atOrUndefined(isolate, 2);
if (!IsCallable(*run_under_lock)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNotCallable, run_under_lock));
}
DirectHandle<Object> timeout_obj = args.atOrUndefined(isolate, 3);
std::optional<base::TimeDelta> timeout;
if (!IsNumber(*timeout_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kIsNotNumber, timeout_obj,
Object::TypeOf(isolate, timeout_obj)));
}
timeout = GetTimeoutDelta(timeout_obj);
// Like Atomics.wait, synchronous locking may block, and so is disallowed on
// the main thread.
//
// This is not a recursive lock, so also throw if recursively locking.
if (!isolate->allow_atomics_wait() || js_mutex->IsCurrentThreadOwner()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kAtomicsOperationNotAllowed,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
DirectHandle<Object> callback_result;
bool success;
{
JSAtomicsMutex::LockGuard lock_guard(isolate, js_mutex, timeout);
if (V8_LIKELY(lock_guard.locked())) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, callback_result,
Execution::Call(isolate, run_under_lock,
isolate->factory()->undefined_value(), {}));
success = true;
} else {
callback_result = isolate->factory()->undefined_value();
success = false;
}
}
DirectHandle<JSObject> result =
JSAtomicsMutex::CreateResultObject(isolate, callback_result, success);
return *result;
}
BUILTIN(AtomicsMutexLockAsync) {
DCHECK(v8_flags.harmony_struct);
constexpr char method_name[] = "Atomics.Mutex.lockAsync";
HandleScope scope(isolate);
DirectHandle<Object> js_mutex_obj = args.atOrUndefined(isolate, 1);
if (!IsJSAtomicsMutex(*js_mutex_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
DirectHandle<JSAtomicsMutex> js_mutex = Cast<JSAtomicsMutex>(js_mutex_obj);
DirectHandle<Object> run_under_lock = args.atOrUndefined(isolate, 2);
if (!IsCallable(*run_under_lock)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNotCallable, run_under_lock));
}
DirectHandle<Object> timeout_obj = args.atOrUndefined(isolate, 3);
std::optional<base::TimeDelta> timeout = std::nullopt;
if (!IsUndefined(*timeout_obj, isolate)) {
if (!IsNumber(*timeout_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kIsNotNumber, timeout_obj,
Object::TypeOf(isolate, timeout_obj)));
}
timeout = GetTimeoutDelta(timeout_obj);
}
DirectHandle<JSPromise> result_promise;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result_promise,
JSAtomicsMutex::LockOrEnqueuePromise(isolate, js_mutex, run_under_lock,
timeout));
return *result_promise;
}
BUILTIN(AtomicsMutexAsyncUnlockResolveHandler) {
DCHECK(v8_flags.harmony_struct);
HandleScope scope(isolate);
DirectHandle<Object> previous_result = args.atOrUndefined(isolate, 1);
DirectHandle<JSPromise> js_unlock_promise =
UnlockAsyncLockedMutexFromPromiseHandler(isolate);
DirectHandle<JSObject> result =
JSAtomicsMutex::CreateResultObject(isolate, previous_result, true);
auto resolve_result = JSPromise::Resolve(js_unlock_promise, result);
USE(resolve_result);
return *isolate->factory()->undefined_value();
}
BUILTIN(AtomicsMutexAsyncUnlockRejectHandler) {
DCHECK(v8_flags.harmony_struct);
HandleScope scope(isolate);
DirectHandle<Object> error = args.atOrUndefined(isolate, 1);
DirectHandle<JSPromise> js_unlock_promise =
UnlockAsyncLockedMutexFromPromiseHandler(isolate);
auto reject_result = JSPromise::Reject(js_unlock_promise, error);
USE(reject_result);
return *isolate->factory()->undefined_value();
}
BUILTIN(AtomicsConditionConstructor) {
DCHECK(v8_flags.harmony_struct);
HandleScope scope(isolate);
return *isolate->factory()->NewJSAtomicsCondition();
}
BUILTIN(AtomicsConditionWait) {
DCHECK(v8_flags.harmony_struct);
constexpr char method_name[] = "Atomics.Condition.wait";
HandleScope scope(isolate);
DirectHandle<Object> js_condition_obj = args.atOrUndefined(isolate, 1);
DirectHandle<Object> js_mutex_obj = args.atOrUndefined(isolate, 2);
DirectHandle<Object> timeout_obj = args.atOrUndefined(isolate, 3);
if (!IsJSAtomicsCondition(*js_condition_obj) ||
!IsJSAtomicsMutex(*js_mutex_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
std::optional<base::TimeDelta> timeout = std::nullopt;
if (!IsUndefined(*timeout_obj, isolate)) {
if (!IsNumber(*timeout_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kIsNotNumber, timeout_obj,
Object::TypeOf(isolate, timeout_obj)));
}
timeout = GetTimeoutDelta(timeout_obj);
}
if (!isolate->allow_atomics_wait()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kAtomicsOperationNotAllowed,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
auto js_condition = Cast<JSAtomicsCondition>(js_condition_obj);
auto js_mutex = Cast<JSAtomicsMutex>(js_mutex_obj);
if (!js_mutex->IsCurrentThreadOwner()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewTypeError(MessageTemplate::kAtomicsMutexNotOwnedByCurrentThread));
}
return isolate->heap()->ToBoolean(
JSAtomicsCondition::WaitFor(isolate, js_condition, js_mutex, timeout));
}
BUILTIN(AtomicsConditionNotify) {
DCHECK(v8_flags.harmony_struct);
constexpr char method_name[] = "Atomics.Condition.notify";
HandleScope scope(isolate);
DirectHandle<Object> js_condition_obj = args.atOrUndefined(isolate, 1);
DirectHandle<Object> count_obj = args.atOrUndefined(isolate, 2);
if (!IsJSAtomicsCondition(*js_condition_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
uint32_t count;
if (IsUndefined(*count_obj, isolate)) {
count = JSAtomicsCondition::kAllWaiters;
} else {
double count_double;
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, count_double, Object::IntegerValue(isolate, count_obj));
if (count_double <= 0) {
return Smi::zero();
} else if (count_double > JSAtomicsCondition::kAllWaiters) {
count_double = JSAtomicsCondition::kAllWaiters;
}
count = static_cast<uint32_t>(count_double);
}
auto js_condition = Cast<JSAtomicsCondition>(js_condition_obj);
return *isolate->factory()->NewNumberFromUint(
JSAtomicsCondition::Notify(isolate, js_condition, count));
}
BUILTIN(AtomicsConditionWaitAsync) {
DCHECK(v8_flags.harmony_struct);
constexpr char method_name[] = "Atomics.Condition.waitAsync";
HandleScope scope(isolate);
DirectHandle<Object> js_condition_obj = args.atOrUndefined(isolate, 1);
DirectHandle<Object> js_mutex_obj = args.atOrUndefined(isolate, 2);
if (!IsJSAtomicsCondition(*js_condition_obj) ||
!IsJSAtomicsMutex(*js_mutex_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
method_name)));
}
DirectHandle<Object> timeout_obj = args.atOrUndefined(isolate, 3);
std::optional<base::TimeDelta> timeout = std::nullopt;
if (!IsUndefined(*timeout_obj, isolate)) {
if (!IsNumber(*timeout_obj)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kIsNotNumber, timeout_obj,
Object::TypeOf(isolate, timeout_obj)));
}
timeout = GetTimeoutDelta(timeout_obj);
}
DirectHandle<JSAtomicsCondition> js_condition =
Cast<JSAtomicsCondition>(js_condition_obj);
auto js_mutex = Cast<JSAtomicsMutex>(js_mutex_obj);
if (!js_mutex->IsCurrentThreadOwner()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewTypeError(MessageTemplate::kAtomicsMutexNotOwnedByCurrentThread));
}
DirectHandle<JSReceiver> result_promise;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result_promise,
JSAtomicsCondition::WaitAsync(isolate, js_condition, js_mutex, timeout));
return *result_promise;
}
BUILTIN(AtomicsConditionAcquireLock) {
DCHECK(v8_flags.harmony_struct);
HandleScope scope(isolate);
DirectHandle<Context> context(isolate->context(), isolate);
DirectHandle<Object> js_mutex_obj(
context->get(JSAtomicsCondition::kMutexAsyncContextSlot), isolate);
DirectHandle<JSAtomicsMutex> js_mutex = Cast<JSAtomicsMutex>(js_mutex_obj);
DirectHandle<JSPromise> lock_promise =
JSAtomicsMutex::LockAsyncWrapperForWait(isolate, js_mutex);
return *lock_promise;
}
} // namespace internal
} // namespace v8