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
19 namespace v8 {
20 namespace 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
AtomicIsLockFree(double size)26 inline 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
BUILTIN(AtomicsIsLockFree)43 BUILTIN(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
ValidateIntegerTypedArray( Isolate* isolate, Handle<Object> object, const char* method_name, bool only_int32_and_big_int64 = false)52 V8_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 )
ValidateAtomicAccess( Isolate* isolate, Handle<JSTypedArray> typed_array, Handle<Object> request_index)91 V8_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
112 namespace {
113
GetAddress64(size_t index, size_t byte_offset)114 inline size_t GetAddress64(size_t index, size_t byte_offset) {
115 return (index << 3) + byte_offset;
116 }
117
GetAddress32(size_t index, size_t byte_offset)118 inline 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 )
BUILTIN(AtomicsNotify)126 BUILTIN(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
DoWait(Isolate* isolate, FutexEmulation::WaitMode mode, Handle<Object> array, Handle<Object> index, Handle<Object> value, Handle<Object> timeout)181 Object 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 )
BUILTIN(AtomicsWait)253 BUILTIN(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
BUILTIN(AtomicsWaitAsync)264 BUILTIN(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