| // Copyright 2016 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/base/macros.h" |
| #include "src/base/platform/yield-processor.h" |
| #include "src/builtins/builtins-utils-inl.h" |
| #include "src/common/globals.h" |
| #include "src/execution/futex-emulation.h" |
| #include "src/heap/factory.h" |
| #include "src/logging/counters.h" |
| #include "src/numbers/conversions-inl.h" |
| #include "src/objects/js-array-buffer-inl.h" |
| #include "src/objects/objects-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| // See builtins-arraybuffer.cc for implementations of |
| // SharedArrayBuffer.prototype.byteLength and SharedArrayBuffer.prototype.slice |
| |
| // https://tc39.es/ecma262/#sec-atomics.islockfree |
| inline bool AtomicIsLockFree(double size) { |
| // According to the standard, 1, 2, and 4 byte atomics are supposed to be |
| // 'lock free' on every platform. 'Lock free' means that all possible uses of |
| // those atomics guarantee forward progress for the agent cluster (i.e. all |
| // threads in contrast with a single thread). |
| // |
| // This property is often, but not always, aligned with whether atomic |
| // accesses are implemented with software locks such as mutexes. |
| // |
| // V8 has lock free atomics for all sizes on all supported first-class |
| // architectures: ia32, x64, ARM32 variants, and ARM64. Further, this property |
| // is depended upon by WebAssembly, which prescribes that all atomic accesses |
| // are always lock free. |
| return size == 1 || size == 2 || size == 4 || size == 8; |
| } |
| |
| // https://tc39.es/ecma262/#sec-atomics.islockfree |
| BUILTIN(AtomicsIsLockFree) { |
| HandleScope scope(isolate); |
| Handle<Object> size = args.atOrUndefined(isolate, 1); |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, size, |
| Object::ToNumber(isolate, size)); |
| return *isolate->factory()->ToBoolean( |
| AtomicIsLockFree(Object::NumberValue(*size))); |
| } |
| |
| // https://tc39.es/ecma262/#sec-validatesharedintegertypedarray |
| V8_WARN_UNUSED_RESULT MaybeDirectHandle<JSTypedArray> ValidateIntegerTypedArray( |
| Isolate* isolate, Handle<Object> object, const char* method_name, |
| bool only_int32_and_big_int64 = false) { |
| if (IsJSTypedArray(*object)) { |
| DirectHandle<JSTypedArray> typed_array = Cast<JSTypedArray>(object); |
| |
| if (typed_array->IsDetachedOrOutOfBounds()) { |
| THROW_NEW_ERROR( |
| isolate, NewTypeError(MessageTemplate::kDetachedOperation, |
| isolate->factory()->NewStringFromAsciiChecked( |
| method_name))); |
| } |
| |
| if (only_int32_and_big_int64) { |
| if (typed_array->type() == kExternalInt32Array || |
| typed_array->type() == kExternalBigInt64Array) { |
| return typed_array; |
| } |
| } else { |
| if (typed_array->type() != kExternalFloat32Array && |
| typed_array->type() != kExternalFloat64Array && |
| typed_array->type() != kExternalUint8ClampedArray) |
| return typed_array; |
| } |
| } |
| |
| THROW_NEW_ERROR( |
| isolate, NewTypeError(only_int32_and_big_int64 |
| ? MessageTemplate::kNotInt32OrBigInt64TypedArray |
| : MessageTemplate::kNotIntegerTypedArray, |
| object)); |
| } |
| |
| // https://tc39.es/ecma262/#sec-validateatomicaccess |
| // ValidateAtomicAccess( typedArray, requestIndex ) |
| V8_WARN_UNUSED_RESULT Maybe<size_t> ValidateAtomicAccess( |
| Isolate* isolate, DirectHandle<JSTypedArray> typed_array, |
| Handle<Object> request_index) { |
| DirectHandle<Object> access_index_obj; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, access_index_obj, |
| Object::ToIndex(isolate, request_index, |
| MessageTemplate::kInvalidAtomicAccessIndex), |
| Nothing<size_t>()); |
| |
| size_t access_index; |
| size_t typed_array_length = typed_array->GetLength(); |
| if (!TryNumberToSize(*access_index_obj, &access_index) || |
| access_index >= typed_array_length) { |
| isolate->Throw(*isolate->factory()->NewRangeError( |
| MessageTemplate::kInvalidAtomicAccessIndex)); |
| return Nothing<size_t>(); |
| } |
| return Just<size_t>(access_index); |
| } |
| |
| namespace { |
| |
| inline size_t GetAddress64(size_t index, size_t byte_offset) { |
| return (index << 3) + byte_offset; |
| } |
| |
| inline size_t GetAddress32(size_t index, size_t byte_offset) { |
| return (index << 2) + byte_offset; |
| } |
| |
| } // namespace |
| |
| // ES #sec-atomics.notify |
| // Atomics.notify( typedArray, index, count ) |
| BUILTIN(AtomicsNotify) { |
| // TODO(clemensb): This builtin only allocates (an exception) in the case of |
| // an error; we could try to avoid allocating the HandleScope in the non-error |
| // case. |
| HandleScope scope(isolate); |
| Handle<Object> array = args.atOrUndefined(isolate, 1); |
| Handle<Object> index = args.atOrUndefined(isolate, 2); |
| Handle<Object> count = args.atOrUndefined(isolate, 3); |
| |
| DirectHandle<JSTypedArray> sta; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, sta, |
| ValidateIntegerTypedArray(isolate, array, "Atomics.notify", 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(); |
| |
| // 3. If count is undefined, let c be +∞. |
| // 4. Else, |
| // a. Let intCount be ? ToInteger(count). |
| // b. Let c be max(intCount, 0). |
| uint32_t c; |
| if (IsUndefined(*count, isolate)) { |
| c = kMaxUInt32; |
| } else { |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, count, |
| Object::ToInteger(isolate, count)); |
| double count_double = Object::NumberValue(*count); |
| if (count_double < 0) { |
| count_double = 0; |
| } else if (count_double > kMaxUInt32) { |
| count_double = kMaxUInt32; |
| } |
| c = static_cast<uint32_t>(count_double); |
| } |
| |
| // Steps 5-9 performed in FutexEmulation::Wake. |
| |
| // 10. If IsSharedArrayBuffer(buffer) is false, return 0. |
| DirectHandle<JSArrayBuffer> array_buffer = sta->GetBuffer(); |
| |
| if (V8_UNLIKELY(!array_buffer->is_shared())) { |
| return Smi::zero(); |
| } |
| |
| // Steps 11-17 performed in FutexEmulation::Wake. |
| size_t wake_addr; |
| if (sta->type() == kExternalBigInt64Array) { |
| wake_addr = GetAddress64(i, sta->byte_offset()); |
| } else { |
| DCHECK(sta->type() == kExternalInt32Array); |
| wake_addr = GetAddress32(i, sta->byte_offset()); |
| } |
| int num_waiters_woken = FutexEmulation::Wake(*array_buffer, wake_addr, c); |
| return Smi::FromInt(num_waiters_woken); |
| } |
| |
| Tagged<Object> DoWait(Isolate* isolate, FutexEmulation::WaitMode mode, |
| Handle<Object> array, Handle<Object> index, |
| Handle<Object> value, Handle<Object> timeout) { |
| // 1. Let buffer be ? ValidateIntegerTypedArray(typedArray, true). |
| DirectHandle<JSTypedArray> sta; |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION( |
| isolate, sta, |
| ValidateIntegerTypedArray(isolate, array, "Atomics.wait", true)); |
| |
| // 2. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception. |
| if (V8_UNLIKELY(!sta->GetBuffer()->is_shared())) { |
| THROW_NEW_ERROR_RETURN_FAILURE( |
| isolate, NewTypeError(MessageTemplate::kNotSharedTypedArray, array)); |
| } |
| |
| // 3. 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(); |
| |
| // 4. Let arrayTypeName be typedArray.[[TypedArrayName]]. |
| // 5. If arrayTypeName is "BigInt64Array", let v be ? ToBigInt64(value). |
| // 6. Otherwise, let v be ? ToInt32(value). |
| if (sta->type() == kExternalBigInt64Array) { |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value, |
| BigInt::FromObject(isolate, value)); |
| } else { |
| DCHECK(sta->type() == kExternalInt32Array); |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value, |
| Object::ToInt32(isolate, value)); |
| } |
| |
| // 7. Let q be ? ToNumber(timeout). |
| // 8. If q is NaN, let t be +∞, else let t be max(q, 0). |
| double timeout_number; |
| if (IsUndefined(*timeout, isolate)) { |
| timeout_number = |
| Object::NumberValue(ReadOnlyRoots(isolate).infinity_value()); |
| } else { |
| ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, timeout, |
| Object::ToNumber(isolate, timeout)); |
| timeout_number = Object::NumberValue(*timeout); |
| if (std::isnan(timeout_number)) |
| timeout_number = |
| Object::NumberValue(ReadOnlyRoots(isolate).infinity_value()); |
| else if (timeout_number < 0) |
| timeout_number = 0; |
| } |
| |
| // 9. 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::kAtomicsOperationNotAllowed, |
| isolate->factory()->NewStringFromAsciiChecked( |
| "Atomics.wait"))); |
| } |
| |
| Handle<JSArrayBuffer> array_buffer = sta->GetBuffer(); |
| |
| if (sta->type() == kExternalBigInt64Array) { |
| return FutexEmulation::WaitJs64( |
| isolate, mode, array_buffer, GetAddress64(i, sta->byte_offset()), |
| Cast<BigInt>(value)->AsInt64(), timeout_number); |
| } else { |
| DCHECK(sta->type() == kExternalInt32Array); |
| return FutexEmulation::WaitJs32(isolate, mode, array_buffer, |
| GetAddress32(i, sta->byte_offset()), |
| NumberToInt32(*value), timeout_number); |
| } |
| } |
| |
| // https://tc39.es/ecma262/#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); |
| isolate->CountUsage(v8::Isolate::kAtomicsWaitAsync); |
| |
| return DoWait(isolate, FutexEmulation::WaitMode::kAsync, array, index, value, |
| timeout); |
| } |
| |
| namespace { |
| V8_NOINLINE Maybe<bool> CheckAtomicsPauseIterationNumber( |
| Isolate* isolate, DirectHandle<Object> iteration_number) { |
| constexpr char method_name[] = "Atomics.pause"; |
| |
| // 1. If N is neither undefined nor an integral Number, throw a TypeError |
| // exception. |
| if (IsNumber(*iteration_number)) { |
| double iter = Object::NumberValue(*iteration_number); |
| if (std::isfinite(iter) && nearbyint(iter) == iter) { |
| return Just(true); |
| } |
| } |
| |
| THROW_NEW_ERROR_RETURN_VALUE( |
| isolate, |
| NewError(isolate->type_error_function(), |
| MessageTemplate::kArgumentIsNotUndefinedOrInteger, |
| isolate->factory()->NewStringFromAsciiChecked(method_name)), |
| Nothing<bool>()); |
| } |
| } // namespace |
| |
| // https://tc39.es/proposal-atomics-microwait/ |
| BUILTIN(AtomicsPause) { |
| HandleScope scope(isolate); |
| DirectHandle<Object> iteration_number = args.atOrUndefined(isolate, 1); |
| |
| // 1. If N is neither undefined nor an integral Number, throw a TypeError |
| // exception. |
| if (V8_UNLIKELY(!IsUndefined(*iteration_number, isolate) && |
| !IsSmi(*iteration_number))) { |
| MAYBE_RETURN_ON_EXCEPTION_VALUE( |
| isolate, CheckAtomicsPauseIterationNumber(isolate, iteration_number), |
| ReadOnlyRoots(isolate).exception()); |
| } |
| |
| // 2. If the execution environment of the ECMAScript implementation supports |
| // signaling to the operating system or CPU that the current executing code |
| // is in a spin-wait loop, such as executing a pause CPU instruction, send |
| // that signal. When N is not undefined, it determines the number of times |
| // that signal is sent. The number of times the signal is sent for an |
| // integral Number N is less than or equal to the number times it is sent |
| // for N + 1 if both N and N + 1 have the same sign. |
| // |
| // In the non-inlined version, JS call overhead is sufficiently expensive that |
| // iterationNumber is not used to determine how many times YIELD_PROCESSOR is |
| // performed. |
| // |
| // TODO(352359899): Try to estimate the call overhead and adjust the yield |
| // count while taking iterationNumber into account. |
| YIELD_PROCESSOR; |
| |
| // 3. Return undefined. |
| return ReadOnlyRoots(isolate).undefined_value(); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |