1// Copyright 2016 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/base/macros.h"
6#include "src/base/platform/mutex.h"
7#include "src/base/platform/time.h"
8#include "src/builtins/builtins-utils-inl.h"
9#include "src/builtins/builtins.h"
10#include "src/codegen/code-factory.h"
11#include "src/common/globals.h"
12#include "src/execution/futex-emulation.h"
13#include "src/heap/factory.h"
14#include "src/logging/counters.h"
15#include "src/numbers/conversions-inl.h"
16#include "src/objects/js-array-buffer-inl.h"
17#include "src/objects/objects-inl.h"
18
19namespace v8 {
20namespace internal {
21
22// See builtins-arraybuffer.cc for implementations of
23// SharedArrayBuffer.prototype.byteLength and SharedArrayBuffer.prototype.slice
24
25// https://tc39.es/ecma262/#sec-atomics.islockfree
26inline bool AtomicIsLockFree(double size) {
27  // According to the standard, 1, 2, and 4 byte atomics are supposed to be
28  // 'lock free' on every platform. 'Lock free' means that all possible uses of
29  // those atomics guarantee forward progress for the agent cluster (i.e. all
30  // threads in contrast with a single thread).
31  //
32  // This property is often, but not always, aligned with whether atomic
33  // accesses are implemented with software locks such as mutexes.
34  //
35  // V8 has lock free atomics for all sizes on all supported first-class
36  // architectures: ia32, x64, ARM32 variants, and ARM64. Further, this property
37  // is depended upon by WebAssembly, which prescribes that all atomic accesses
38  // are always lock free.
39  return size == 1 || size == 2 || size == 4 || size == 8;
40}
41
42// https://tc39.es/ecma262/#sec-atomics.islockfree
43BUILTIN(AtomicsIsLockFree) {
44  HandleScope scope(isolate);
45  Handle<Object> size = args.atOrUndefined(isolate, 1);
46  ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, size,
47                                     Object::ToNumber(isolate, size));
48  return *isolate->factory()->ToBoolean(AtomicIsLockFree(size->Number()));
49}
50
51// https://tc39.es/ecma262/#sec-validatesharedintegertypedarray
52V8_WARN_UNUSED_RESULT MaybeHandle<JSTypedArray> ValidateIntegerTypedArray(
53    Isolate* isolate, Handle<Object> object, const char* method_name,
54    bool only_int32_and_big_int64 = false) {
55  if (object->IsJSTypedArray()) {
56    Handle<JSTypedArray> typed_array = Handle<JSTypedArray>::cast(object);
57
58    if (typed_array->WasDetached()) {
59      THROW_NEW_ERROR(
60          isolate,
61          NewTypeError(
62              MessageTemplate::kDetachedOperation,
63              isolate->factory()->NewStringFromAsciiChecked(method_name)),
64          JSTypedArray);
65    }
66
67    if (only_int32_and_big_int64) {
68      if (typed_array->type() == kExternalInt32Array ||
69          typed_array->type() == kExternalBigInt64Array) {
70        return typed_array;
71      }
72    } else {
73      if (typed_array->type() != kExternalFloat32Array &&
74          typed_array->type() != kExternalFloat64Array &&
75          typed_array->type() != kExternalUint8ClampedArray)
76        return typed_array;
77    }
78  }
79
80  THROW_NEW_ERROR(
81      isolate,
82      NewTypeError(only_int32_and_big_int64
83                       ? MessageTemplate::kNotInt32OrBigInt64TypedArray
84                       : MessageTemplate::kNotIntegerTypedArray,
85                   object),
86      JSTypedArray);
87}
88
89// https://tc39.es/ecma262/#sec-validateatomicaccess
90// ValidateAtomicAccess( typedArray, requestIndex )
91V8_WARN_UNUSED_RESULT Maybe<size_t> ValidateAtomicAccess(
92    Isolate* isolate, Handle<JSTypedArray> typed_array,
93    Handle<Object> request_index) {
94  Handle<Object> access_index_obj;
95  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
96      isolate, access_index_obj,
97      Object::ToIndex(isolate, request_index,
98                      MessageTemplate::kInvalidAtomicAccessIndex),
99      Nothing<size_t>());
100
101  size_t access_index;
102  size_t typed_array_length = typed_array->length();
103  if (!TryNumberToSize(*access_index_obj, &access_index) ||
104      access_index >= typed_array_length) {
105    isolate->Throw(*isolate->factory()->NewRangeError(
106        MessageTemplate::kInvalidAtomicAccessIndex));
107    return Nothing<size_t>();
108  }
109  return Just<size_t>(access_index);
110}
111
112namespace {
113
114inline size_t GetAddress64(size_t index, size_t byte_offset) {
115  return (index << 3) + byte_offset;
116}
117
118inline size_t GetAddress32(size_t index, size_t byte_offset) {
119  return (index << 2) + byte_offset;
120}
121
122}  // namespace
123
124// ES #sec-atomics.notify
125// Atomics.notify( typedArray, index, count )
126BUILTIN(AtomicsNotify) {
127  HandleScope scope(isolate);
128  Handle<Object> array = args.atOrUndefined(isolate, 1);
129  Handle<Object> index = args.atOrUndefined(isolate, 2);
130  Handle<Object> count = args.atOrUndefined(isolate, 3);
131
132  Handle<JSTypedArray> sta;
133  ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
134      isolate, sta,
135      ValidateIntegerTypedArray(isolate, array, "Atomics.notify", true));
136
137  // 2. Let i be ? ValidateAtomicAccess(typedArray, index).
138  Maybe<size_t> maybe_index = ValidateAtomicAccess(isolate, sta, index);
139  if (maybe_index.IsNothing()) return ReadOnlyRoots(isolate).exception();
140  size_t i = maybe_index.FromJust();
141
142  // 3. If count is undefined, let c be +∞.
143  // 4. Else,
144  //   a. Let intCount be ? ToInteger(count).
145  //   b. Let c be max(intCount, 0).
146  uint32_t c;
147  if (count->IsUndefined(isolate)) {
148    c = kMaxUInt32;
149  } else {
150    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, count,
151                                       Object::ToInteger(isolate, count));
152    double count_double = count->Number();
153    if (count_double < 0) {
154      count_double = 0;
155    } else if (count_double > kMaxUInt32) {
156      count_double = kMaxUInt32;
157    }
158    c = static_cast<uint32_t>(count_double);
159  }
160
161  // Steps 5-9 performed in FutexEmulation::Wake.
162
163  // 10. If IsSharedArrayBuffer(buffer) is false, return 0.
164  Handle<JSArrayBuffer> array_buffer = sta->GetBuffer();
165  size_t wake_addr;
166
167  if (V8_UNLIKELY(!sta->GetBuffer()->is_shared())) {
168    return Smi::FromInt(0);
169  }
170
171  // Steps 11-17 performed in FutexEmulation::Wake.
172  if (sta->type() == kExternalBigInt64Array) {
173    wake_addr = GetAddress64(i, sta->byte_offset());
174  } else {
175    DCHECK(sta->type() == kExternalInt32Array);
176    wake_addr = GetAddress32(i, sta->byte_offset());
177  }
178  return FutexEmulation::Wake(array_buffer, wake_addr, c);
179}
180
181Object DoWait(Isolate* isolate, FutexEmulation::WaitMode mode,
182              Handle<Object> array, Handle<Object> index, Handle<Object> value,
183              Handle<Object> timeout) {
184  // 1. Let buffer be ? ValidateIntegerTypedArray(typedArray, true).
185  Handle<JSTypedArray> sta;
186  ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
187      isolate, sta,
188      ValidateIntegerTypedArray(isolate, array, "Atomics.wait", true));
189
190  // 2. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
191  if (V8_UNLIKELY(!sta->GetBuffer()->is_shared())) {
192    THROW_NEW_ERROR_RETURN_FAILURE(
193        isolate, NewTypeError(MessageTemplate::kNotSharedTypedArray, array));
194  }
195
196  // 3. Let i be ? ValidateAtomicAccess(typedArray, index).
197  Maybe<size_t> maybe_index = ValidateAtomicAccess(isolate, sta, index);
198  if (maybe_index.IsNothing()) return ReadOnlyRoots(isolate).exception();
199  size_t i = maybe_index.FromJust();
200
201  // 4. Let arrayTypeName be typedArray.[[TypedArrayName]].
202  // 5. If arrayTypeName is "BigInt64Array", let v be ? ToBigInt64(value).
203  // 6. Otherwise, let v be ? ToInt32(value).
204  if (sta->type() == kExternalBigInt64Array) {
205    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value,
206                                       BigInt::FromObject(isolate, value));
207  } else {
208    DCHECK(sta->type() == kExternalInt32Array);
209    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, value,
210                                       Object::ToInt32(isolate, value));
211  }
212
213  // 7. Let q be ? ToNumber(timeout).
214  // 8. If q is NaN, let t be +∞, else let t be max(q, 0).
215  double timeout_number;
216  if (timeout->IsUndefined(isolate)) {
217    timeout_number = ReadOnlyRoots(isolate).infinity_value().Number();
218  } else {
219    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, timeout,
220                                       Object::ToNumber(isolate, timeout));
221    timeout_number = timeout->Number();
222    if (std::isnan(timeout_number))
223      timeout_number = ReadOnlyRoots(isolate).infinity_value().Number();
224    else if (timeout_number < 0)
225      timeout_number = 0;
226  }
227
228  // 9. If mode is sync, then
229  //   a. Let B be AgentCanSuspend().
230  //   b. If B is false, throw a TypeError exception.
231  if (mode == FutexEmulation::WaitMode::kSync &&
232      !isolate->allow_atomics_wait()) {
233    THROW_NEW_ERROR_RETURN_FAILURE(
234        isolate, NewTypeError(MessageTemplate::kAtomicsWaitNotAllowed));
235  }
236
237  Handle<JSArrayBuffer> array_buffer = sta->GetBuffer();
238
239  if (sta->type() == kExternalBigInt64Array) {
240    return FutexEmulation::WaitJs64(
241        isolate, mode, array_buffer, GetAddress64(i, sta->byte_offset()),
242        Handle<BigInt>::cast(value)->AsInt64(), timeout_number);
243  } else {
244    DCHECK(sta->type() == kExternalInt32Array);
245    return FutexEmulation::WaitJs32(isolate, mode, array_buffer,
246                                    GetAddress32(i, sta->byte_offset()),
247                                    NumberToInt32(*value), timeout_number);
248  }
249}
250
251// https://tc39.es/ecma262/#sec-atomics.wait
252// Atomics.wait( typedArray, index, value, timeout )
253BUILTIN(AtomicsWait) {
254  HandleScope scope(isolate);
255  Handle<Object> array = args.atOrUndefined(isolate, 1);
256  Handle<Object> index = args.atOrUndefined(isolate, 2);
257  Handle<Object> value = args.atOrUndefined(isolate, 3);
258  Handle<Object> timeout = args.atOrUndefined(isolate, 4);
259
260  return DoWait(isolate, FutexEmulation::WaitMode::kSync, array, index, value,
261                timeout);
262}
263
264BUILTIN(AtomicsWaitAsync) {
265  HandleScope scope(isolate);
266  Handle<Object> array = args.atOrUndefined(isolate, 1);
267  Handle<Object> index = args.atOrUndefined(isolate, 2);
268  Handle<Object> value = args.atOrUndefined(isolate, 3);
269  Handle<Object> timeout = args.atOrUndefined(isolate, 4);
270
271  return DoWait(isolate, FutexEmulation::WaitMode::kAsync, array, index, value,
272                timeout);
273}
274
275}  // namespace internal
276}  // namespace v8
277