1 // Copyright 2015 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/execution/futex-emulation.h"
6
7 #include <limits>
8
9 #include "src/api/api-inl.h"
10 #include "src/base/logging.h"
11 #include "src/base/macros.h"
12 #include "src/execution/isolate.h"
13 #include "src/execution/vm-state-inl.h"
14 #include "src/handles/handles-inl.h"
15 #include "src/numbers/conversions.h"
16 #include "src/objects/bigint.h"
17 #include "src/objects/js-array-buffer-inl.h"
18 #include "src/objects/js-promise-inl.h"
19 #include "src/objects/objects-inl.h"
20 #include "src/tasks/cancelable-task.h"
21
22 namespace v8 {
23 namespace internal {
24
25 using AtomicsWaitEvent = v8::Isolate::AtomicsWaitEvent;
26
27 class FutexWaitList {
28 public:
29 FutexWaitList() = default;
30 FutexWaitList(const FutexWaitList&) = delete;
31 FutexWaitList& operator=(const FutexWaitList&) = delete;
32
33 void AddNode(FutexWaitListNode* node);
34 void RemoveNode(FutexWaitListNode* node);
35
ToWaitLocation(const BackingStore * backing_store,size_t addr)36 static int8_t* ToWaitLocation(const BackingStore* backing_store,
37 size_t addr) {
38 return static_cast<int8_t*>(backing_store->buffer_start()) + addr;
39 }
40
41 // Deletes "node" and returns the next node of its list.
DeleteAsyncWaiterNode(FutexWaitListNode * node)42 static FutexWaitListNode* DeleteAsyncWaiterNode(FutexWaitListNode* node) {
43 DCHECK_NOT_NULL(node->isolate_for_async_waiters_);
44 auto next = node->next_;
45 if (node->prev_ != nullptr) {
46 node->prev_->next_ = next;
47 }
48 if (next != nullptr) {
49 next->prev_ = node->prev_;
50 }
51 delete node;
52 return next;
53 }
54
DeleteNodesForIsolate(Isolate * isolate,FutexWaitListNode ** head,FutexWaitListNode ** tail)55 static void DeleteNodesForIsolate(Isolate* isolate, FutexWaitListNode** head,
56 FutexWaitListNode** tail) {
57 // For updating head & tail once we've iterated all nodes.
58 FutexWaitListNode* new_head = nullptr;
59 FutexWaitListNode* new_tail = nullptr;
60 auto node = *head;
61 while (node != nullptr) {
62 if (node->isolate_for_async_waiters_ == isolate) {
63 node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
64 node = DeleteAsyncWaiterNode(node);
65 } else {
66 if (new_head == nullptr) {
67 new_head = node;
68 }
69 new_tail = node;
70 node = node->next_;
71 }
72 }
73 *head = new_head;
74 *tail = new_tail;
75 }
76
77 // For checking the internal consistency of the FutexWaitList.
78 void Verify();
79 // Verifies the local consistency of |node|. If it's the first node of its
80 // list, it must be |head|, and if it's the last node, it must be |tail|.
81 void VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
82 FutexWaitListNode* tail);
83 // Returns true if |node| is on the linked list starting with |head|.
84 static bool NodeIsOnList(FutexWaitListNode* node, FutexWaitListNode* head);
85
86 private:
87 friend class FutexEmulation;
88
89 struct HeadAndTail {
90 FutexWaitListNode* head;
91 FutexWaitListNode* tail;
92 };
93 // Location inside a shared buffer -> linked list of Nodes waiting on that
94 // location.
95 std::map<int8_t*, HeadAndTail> location_lists_;
96
97 // Isolate* -> linked list of Nodes which are waiting for their Promises to
98 // be resolved.
99 std::map<Isolate*, HeadAndTail> isolate_promises_to_resolve_;
100 };
101
102 namespace {
103 // `g_mutex` protects the composition of `g_wait_list` (i.e. no elements may be
104 // added or removed without holding this mutex), as well as the `waiting_`
105 // and `interrupted_` fields for each individual list node that is currently
106 // part of the list. It must be the mutex used together with the `cond_`
107 // condition variable of such nodes.
108 base::LazyMutex g_mutex = LAZY_MUTEX_INITIALIZER;
109 base::LazyInstance<FutexWaitList>::type g_wait_list = LAZY_INSTANCE_INITIALIZER;
110 } // namespace
111
~FutexWaitListNode()112 FutexWaitListNode::~FutexWaitListNode() {
113 // Assert that the timeout task was cancelled.
114 DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, timeout_task_id_);
115 }
116
CancelTimeoutTask()117 bool FutexWaitListNode::CancelTimeoutTask() {
118 if (timeout_task_id_ != CancelableTaskManager::kInvalidTaskId) {
119 auto return_value = cancelable_task_manager_->TryAbort(timeout_task_id_);
120 timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
121 return return_value != TryAbortResult::kTaskRunning;
122 }
123 return true;
124 }
125
NotifyWake()126 void FutexWaitListNode::NotifyWake() {
127 DCHECK(!IsAsync());
128 // Lock the FutexEmulation mutex before notifying. We know that the mutex
129 // will have been unlocked if we are currently waiting on the condition
130 // variable. The mutex will not be locked if FutexEmulation::Wait hasn't
131 // locked it yet. In that case, we set the interrupted_
132 // flag to true, which will be tested after the mutex locked by a future wait.
133 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
134
135 // if not waiting, this will not have any effect.
136 cond_.NotifyOne();
137 interrupted_ = true;
138 }
139
140 class ResolveAsyncWaiterPromisesTask : public CancelableTask {
141 public:
ResolveAsyncWaiterPromisesTask(CancelableTaskManager * cancelable_task_manager,Isolate * isolate)142 ResolveAsyncWaiterPromisesTask(CancelableTaskManager* cancelable_task_manager,
143 Isolate* isolate)
144 : CancelableTask(cancelable_task_manager), isolate_(isolate) {}
145
RunInternal()146 void RunInternal() override {
147 FutexEmulation::ResolveAsyncWaiterPromises(isolate_);
148 }
149
150 private:
151 Isolate* isolate_;
152 };
153
154 class AsyncWaiterTimeoutTask : public CancelableTask {
155 public:
AsyncWaiterTimeoutTask(CancelableTaskManager * cancelable_task_manager,FutexWaitListNode * node)156 AsyncWaiterTimeoutTask(CancelableTaskManager* cancelable_task_manager,
157 FutexWaitListNode* node)
158 : CancelableTask(cancelable_task_manager), node_(node) {}
159
RunInternal()160 void RunInternal() override {
161 FutexEmulation::HandleAsyncWaiterTimeout(node_);
162 }
163
164 private:
165 FutexWaitListNode* node_;
166 };
167
NotifyAsyncWaiter(FutexWaitListNode * node)168 void FutexEmulation::NotifyAsyncWaiter(FutexWaitListNode* node) {
169 // This function can run in any thread.
170
171 g_mutex.Pointer()->AssertHeld();
172
173 // Nullify the timeout time; this distinguishes timed out waiters from
174 // woken up ones.
175 node->async_timeout_time_ = base::TimeTicks();
176
177 g_wait_list.Pointer()->RemoveNode(node);
178
179 // Schedule a task for resolving the Promise. It's still possible that the
180 // timeout task runs before the promise resolving task. In that case, the
181 // timeout task will just ignore the node.
182 auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
183 auto it = isolate_map.find(node->isolate_for_async_waiters_);
184 if (it == isolate_map.end()) {
185 // This Isolate doesn't have other Promises to resolve at the moment.
186 isolate_map.insert(std::make_pair(node->isolate_for_async_waiters_,
187 FutexWaitList::HeadAndTail{node, node}));
188 auto task = std::make_unique<ResolveAsyncWaiterPromisesTask>(
189 node->cancelable_task_manager_, node->isolate_for_async_waiters_);
190 node->task_runner_->PostNonNestableTask(std::move(task));
191 } else {
192 // Add this Node into the existing list.
193 node->prev_ = it->second.tail;
194 it->second.tail->next_ = node;
195 it->second.tail = node;
196 }
197 }
198
AddNode(FutexWaitListNode * node)199 void FutexWaitList::AddNode(FutexWaitListNode* node) {
200 DCHECK_NULL(node->prev_);
201 DCHECK_NULL(node->next_);
202 auto it = location_lists_.find(node->wait_location_);
203 if (it == location_lists_.end()) {
204 location_lists_.insert(
205 std::make_pair(node->wait_location_, HeadAndTail{node, node}));
206 } else {
207 it->second.tail->next_ = node;
208 node->prev_ = it->second.tail;
209 it->second.tail = node;
210 }
211
212 Verify();
213 }
214
RemoveNode(FutexWaitListNode * node)215 void FutexWaitList::RemoveNode(FutexWaitListNode* node) {
216 auto it = location_lists_.find(node->wait_location_);
217 DCHECK_NE(location_lists_.end(), it);
218 DCHECK(NodeIsOnList(node, it->second.head));
219
220 if (node->prev_) {
221 node->prev_->next_ = node->next_;
222 } else {
223 DCHECK_EQ(node, it->second.head);
224 it->second.head = node->next_;
225 }
226
227 if (node->next_) {
228 node->next_->prev_ = node->prev_;
229 } else {
230 DCHECK_EQ(node, it->second.tail);
231 it->second.tail = node->prev_;
232 }
233
234 // If the node was the last one on its list, delete the whole list.
235 if (node->prev_ == nullptr && node->next_ == nullptr) {
236 location_lists_.erase(it);
237 }
238
239 node->prev_ = node->next_ = nullptr;
240
241 Verify();
242 }
243
Wake()244 void AtomicsWaitWakeHandle::Wake() {
245 // Adding a separate `NotifyWake()` variant that doesn't acquire the lock
246 // itself would likely just add unnecessary complexity..
247 // The split lock by itself isn’t an issue, as long as the caller properly
248 // synchronizes this with the closing `AtomicsWaitCallback`.
249 {
250 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
251 stopped_ = true;
252 }
253 isolate_->futex_wait_list_node()->NotifyWake();
254 }
255
256 enum WaitReturnValue : int { kOk = 0, kNotEqual = 1, kTimedOut = 2 };
257
258 namespace {
259
WaitJsTranslateReturn(Isolate * isolate,Object res)260 Object WaitJsTranslateReturn(Isolate* isolate, Object res) {
261 if (res.IsSmi()) {
262 int val = Smi::ToInt(res);
263 switch (val) {
264 case WaitReturnValue::kOk:
265 return ReadOnlyRoots(isolate).ok_string();
266 case WaitReturnValue::kNotEqual:
267 return ReadOnlyRoots(isolate).not_equal_string();
268 case WaitReturnValue::kTimedOut:
269 return ReadOnlyRoots(isolate).timed_out_string();
270 default:
271 UNREACHABLE();
272 }
273 }
274 return res;
275 }
276
277 } // namespace
278
WaitJs32(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,int32_t value,double rel_timeout_ms)279 Object FutexEmulation::WaitJs32(Isolate* isolate, WaitMode mode,
280 Handle<JSArrayBuffer> array_buffer, size_t addr,
281 int32_t value, double rel_timeout_ms) {
282 Object res =
283 Wait<int32_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
284 return WaitJsTranslateReturn(isolate, res);
285 }
286
WaitJs64(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,int64_t value,double rel_timeout_ms)287 Object FutexEmulation::WaitJs64(Isolate* isolate, WaitMode mode,
288 Handle<JSArrayBuffer> array_buffer, size_t addr,
289 int64_t value, double rel_timeout_ms) {
290 Object res =
291 Wait<int64_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
292 return WaitJsTranslateReturn(isolate, res);
293 }
294
WaitWasm32(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,int32_t value,int64_t rel_timeout_ns)295 Object FutexEmulation::WaitWasm32(Isolate* isolate,
296 Handle<JSArrayBuffer> array_buffer,
297 size_t addr, int32_t value,
298 int64_t rel_timeout_ns) {
299 return Wait<int32_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
300 rel_timeout_ns >= 0, rel_timeout_ns, CallType::kIsWasm);
301 }
302
WaitWasm64(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,int64_t value,int64_t rel_timeout_ns)303 Object FutexEmulation::WaitWasm64(Isolate* isolate,
304 Handle<JSArrayBuffer> array_buffer,
305 size_t addr, int64_t value,
306 int64_t rel_timeout_ns) {
307 return Wait<int64_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
308 rel_timeout_ns >= 0, rel_timeout_ns, CallType::kIsWasm);
309 }
310
311 template <typename T>
Wait(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,double rel_timeout_ms)312 Object FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
313 Handle<JSArrayBuffer> array_buffer, size_t addr,
314 T value, double rel_timeout_ms) {
315 DCHECK_LT(addr, array_buffer->byte_length());
316
317 bool use_timeout = rel_timeout_ms != V8_INFINITY;
318 int64_t rel_timeout_ns = -1;
319
320 if (use_timeout) {
321 // Convert to nanoseconds.
322 double timeout_ns = rel_timeout_ms *
323 base::Time::kNanosecondsPerMicrosecond *
324 base::Time::kMicrosecondsPerMillisecond;
325 if (timeout_ns > static_cast<double>(std::numeric_limits<int64_t>::max())) {
326 // 2**63 nanoseconds is 292 years. Let's just treat anything greater as
327 // infinite.
328 use_timeout = false;
329 } else {
330 rel_timeout_ns = static_cast<int64_t>(timeout_ns);
331 }
332 }
333 return Wait(isolate, mode, array_buffer, addr, value, use_timeout,
334 rel_timeout_ns);
335 }
336
337 namespace {
WaitTimeoutInMs(double timeout_ns)338 double WaitTimeoutInMs(double timeout_ns) {
339 return timeout_ns < 0
340 ? V8_INFINITY
341 : timeout_ns / (base::Time::kNanosecondsPerMicrosecond *
342 base::Time::kMicrosecondsPerMillisecond);
343 }
344 } // namespace
345
346 template <typename T>
Wait(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns,CallType call_type)347 Object FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
348 Handle<JSArrayBuffer> array_buffer, size_t addr,
349 T value, bool use_timeout, int64_t rel_timeout_ns,
350 CallType call_type) {
351 if (mode == WaitMode::kSync) {
352 return WaitSync(isolate, array_buffer, addr, value, use_timeout,
353 rel_timeout_ns, call_type);
354 }
355 DCHECK_EQ(mode, WaitMode::kAsync);
356 return WaitAsync(isolate, array_buffer, addr, value, use_timeout,
357 rel_timeout_ns, call_type);
358 }
359
360 template <typename T>
WaitSync(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns,CallType call_type)361 Object FutexEmulation::WaitSync(Isolate* isolate,
362 Handle<JSArrayBuffer> array_buffer, size_t addr,
363 T value, bool use_timeout,
364 int64_t rel_timeout_ns, CallType call_type) {
365 VMState<ATOMICS_WAIT> state(isolate);
366 base::TimeDelta rel_timeout =
367 base::TimeDelta::FromNanoseconds(rel_timeout_ns);
368
369 // We have to convert the timeout back to double for the AtomicsWaitCallback.
370 double rel_timeout_ms = WaitTimeoutInMs(static_cast<double>(rel_timeout_ns));
371 AtomicsWaitWakeHandle stop_handle(isolate);
372
373 isolate->RunAtomicsWaitCallback(AtomicsWaitEvent::kStartWait, array_buffer,
374 addr, value, rel_timeout_ms, &stop_handle);
375
376 if (isolate->has_scheduled_exception()) {
377 return isolate->PromoteScheduledException();
378 }
379
380 Handle<Object> result;
381 AtomicsWaitEvent callback_result = AtomicsWaitEvent::kWokenUp;
382
383 do { // Not really a loop, just makes it easier to break out early.
384 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
385
386 std::shared_ptr<BackingStore> backing_store =
387 array_buffer->GetBackingStore();
388 DCHECK(backing_store);
389 FutexWaitListNode* node = isolate->futex_wait_list_node();
390 node->backing_store_ = backing_store;
391 node->wait_addr_ = addr;
392 auto wait_location =
393 FutexWaitList::ToWaitLocation(backing_store.get(), addr);
394 node->wait_location_ = wait_location;
395 node->waiting_ = true;
396
397 // Reset node->waiting_ = false when leaving this scope (but while
398 // still holding the lock).
399 FutexWaitListNode::ResetWaitingOnScopeExit reset_waiting(node);
400
401 std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(wait_location);
402 T loaded_value = p->load();
403 #if defined(V8_TARGET_BIG_ENDIAN)
404 // If loading a Wasm value, it needs to be reversed on Big Endian platforms.
405 if (call_type == CallType::kIsWasm) {
406 DCHECK(sizeof(T) == kInt32Size || sizeof(T) == kInt64Size);
407 loaded_value = ByteReverse(loaded_value);
408 }
409 #endif
410 if (loaded_value != value) {
411 result = handle(Smi::FromInt(WaitReturnValue::kNotEqual), isolate);
412 callback_result = AtomicsWaitEvent::kNotEqual;
413 break;
414 }
415
416 base::TimeTicks timeout_time;
417 base::TimeTicks current_time;
418
419 if (use_timeout) {
420 current_time = base::TimeTicks::Now();
421 timeout_time = current_time + rel_timeout;
422 }
423
424 g_wait_list.Pointer()->AddNode(node);
425
426 while (true) {
427 bool interrupted = node->interrupted_;
428 node->interrupted_ = false;
429
430 // Unlock the mutex here to prevent deadlock from lock ordering between
431 // mutex and mutexes locked by HandleInterrupts.
432 lock_guard.Unlock();
433
434 // Because the mutex is unlocked, we have to be careful about not dropping
435 // an interrupt. The notification can happen in three different places:
436 // 1) Before Wait is called: the notification will be dropped, but
437 // interrupted_ will be set to 1. This will be checked below.
438 // 2) After interrupted has been checked here, but before mutex is
439 // acquired: interrupted is checked again below, with mutex locked.
440 // Because the wakeup signal also acquires mutex, we know it will not
441 // be able to notify until mutex is released below, when waiting on
442 // the condition variable.
443 // 3) After the mutex is released in the call to WaitFor(): this
444 // notification will wake up the condition variable. node->waiting() will
445 // be false, so we'll loop and then check interrupts.
446 if (interrupted) {
447 Object interrupt_object = isolate->stack_guard()->HandleInterrupts();
448 if (interrupt_object.IsException(isolate)) {
449 result = handle(interrupt_object, isolate);
450 callback_result = AtomicsWaitEvent::kTerminatedExecution;
451 lock_guard.Lock();
452 break;
453 }
454 }
455
456 lock_guard.Lock();
457
458 if (node->interrupted_) {
459 // An interrupt occurred while the mutex was unlocked. Don't wait yet.
460 continue;
461 }
462
463 if (stop_handle.has_stopped()) {
464 node->waiting_ = false;
465 callback_result = AtomicsWaitEvent::kAPIStopped;
466 }
467
468 if (!node->waiting_) {
469 result = handle(Smi::FromInt(WaitReturnValue::kOk), isolate);
470 break;
471 }
472
473 // No interrupts, now wait.
474 if (use_timeout) {
475 current_time = base::TimeTicks::Now();
476 if (current_time >= timeout_time) {
477 result = handle(Smi::FromInt(WaitReturnValue::kTimedOut), isolate);
478 callback_result = AtomicsWaitEvent::kTimedOut;
479 break;
480 }
481
482 base::TimeDelta time_until_timeout = timeout_time - current_time;
483 DCHECK_GE(time_until_timeout.InMicroseconds(), 0);
484 bool wait_for_result =
485 node->cond_.WaitFor(g_mutex.Pointer(), time_until_timeout);
486 USE(wait_for_result);
487 } else {
488 node->cond_.Wait(g_mutex.Pointer());
489 }
490
491 // Spurious wakeup, interrupt or timeout.
492 }
493
494 g_wait_list.Pointer()->RemoveNode(node);
495 } while (false);
496
497 isolate->RunAtomicsWaitCallback(callback_result, array_buffer, addr, value,
498 rel_timeout_ms, nullptr);
499
500 if (isolate->has_scheduled_exception()) {
501 CHECK_NE(callback_result, AtomicsWaitEvent::kTerminatedExecution);
502 result = handle(isolate->PromoteScheduledException(), isolate);
503 }
504
505 return *result;
506 }
507
FutexWaitListNode(const std::shared_ptr<BackingStore> & backing_store,size_t wait_addr,Handle<JSObject> promise,Isolate * isolate)508 FutexWaitListNode::FutexWaitListNode(
509 const std::shared_ptr<BackingStore>& backing_store, size_t wait_addr,
510 Handle<JSObject> promise, Isolate* isolate)
511 : isolate_for_async_waiters_(isolate),
512 backing_store_(backing_store),
513 wait_addr_(wait_addr),
514 wait_location_(
515 FutexWaitList::ToWaitLocation(backing_store.get(), wait_addr)),
516 waiting_(true) {
517 auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
518 task_runner_ = V8::GetCurrentPlatform()->GetForegroundTaskRunner(v8_isolate);
519 cancelable_task_manager_ = isolate->cancelable_task_manager();
520
521 v8::Local<v8::Promise> local_promise = Utils::PromiseToLocal(promise);
522 promise_.Reset(v8_isolate, local_promise);
523 promise_.SetWeak();
524 Handle<NativeContext> native_context(isolate->native_context());
525 v8::Local<v8::Context> local_native_context =
526 Utils::ToLocal(Handle<Context>::cast(native_context));
527 native_context_.Reset(v8_isolate, local_native_context);
528 native_context_.SetWeak();
529 }
530
531 template <typename T>
WaitAsync(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns,CallType call_type)532 Object FutexEmulation::WaitAsync(Isolate* isolate,
533 Handle<JSArrayBuffer> array_buffer,
534 size_t addr, T value, bool use_timeout,
535 int64_t rel_timeout_ns, CallType call_type) {
536 base::TimeDelta rel_timeout =
537 base::TimeDelta::FromNanoseconds(rel_timeout_ns);
538
539 Factory* factory = isolate->factory();
540 Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
541 Handle<JSObject> promise_capability = factory->NewJSPromise();
542
543 enum class ResultKind { kNotEqual, kTimedOut, kAsync };
544 ResultKind result_kind;
545 {
546 // 16. Perform EnterCriticalSection(WL).
547 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
548
549 std::shared_ptr<BackingStore> backing_store =
550 array_buffer->GetBackingStore();
551
552 // 17. Let w be ! AtomicLoad(typedArray, i).
553 std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(
554 static_cast<int8_t*>(backing_store->buffer_start()) + addr);
555 T loaded_value = p->load();
556 #if defined(V8_TARGET_BIG_ENDIAN)
557 // If loading a Wasm value, it needs to be reversed on Big Endian platforms.
558 if (call_type == CallType::kIsWasm) {
559 DCHECK(sizeof(T) == kInt32Size || sizeof(T) == kInt64Size);
560 loaded_value = ByteReverse(loaded_value);
561 }
562 #endif
563 if (loaded_value != value) {
564 result_kind = ResultKind::kNotEqual;
565 } else if (use_timeout && rel_timeout_ns == 0) {
566 result_kind = ResultKind::kTimedOut;
567 } else {
568 result_kind = ResultKind::kAsync;
569
570 FutexWaitListNode* node = new FutexWaitListNode(
571 backing_store, addr, promise_capability, isolate);
572
573 if (use_timeout) {
574 node->async_timeout_time_ = base::TimeTicks::Now() + rel_timeout;
575 auto task = std::make_unique<AsyncWaiterTimeoutTask>(
576 node->cancelable_task_manager_, node);
577 node->timeout_task_id_ = task->id();
578 node->task_runner_->PostNonNestableDelayedTask(
579 std::move(task), rel_timeout.InSecondsF());
580 }
581
582 g_wait_list.Pointer()->AddNode(node);
583 }
584
585 // Leaving the block collapses the following steps:
586 // 18.a. Perform LeaveCriticalSection(WL).
587 // 19.b. Perform LeaveCriticalSection(WL).
588 // 24. Perform LeaveCriticalSection(WL).
589 }
590
591 switch (result_kind) {
592 case ResultKind::kNotEqual:
593 // 18. If v is not equal to w, then
594 // ...
595 // c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
596 // d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
597 // "not-equal").
598 // e. Return resultObject.
599 CHECK(JSReceiver::CreateDataProperty(
600 isolate, result, factory->async_string(),
601 factory->false_value(), Just(kDontThrow))
602 .FromJust());
603 CHECK(JSReceiver::CreateDataProperty(
604 isolate, result, factory->value_string(),
605 factory->not_equal_string(), Just(kDontThrow))
606 .FromJust());
607 break;
608
609 case ResultKind::kTimedOut:
610 // 19. If t is 0 and mode is async, then
611 // ...
612 // c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
613 // d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
614 // "timed-out").
615 // e. Return resultObject.
616 CHECK(JSReceiver::CreateDataProperty(
617 isolate, result, factory->async_string(),
618 factory->false_value(), Just(kDontThrow))
619 .FromJust());
620 CHECK(JSReceiver::CreateDataProperty(
621 isolate, result, factory->value_string(),
622 factory->timed_out_string(), Just(kDontThrow))
623 .FromJust());
624 break;
625
626 case ResultKind::kAsync:
627 // Add the Promise into the NativeContext's atomics_waitasync_promises
628 // set, so that the list keeps it alive.
629 Handle<NativeContext> native_context(isolate->native_context());
630 Handle<OrderedHashSet> promises(
631 native_context->atomics_waitasync_promises(), isolate);
632 promises = OrderedHashSet::Add(isolate, promises, promise_capability)
633 .ToHandleChecked();
634 native_context->set_atomics_waitasync_promises(*promises);
635
636 // 26. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
637 // 27. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
638 // promiseCapability.[[Promise]]).
639 // 28. Return resultObject.
640 CHECK(JSReceiver::CreateDataProperty(
641 isolate, result, factory->async_string(), factory->true_value(),
642 Just(kDontThrow))
643 .FromJust());
644 CHECK(JSReceiver::CreateDataProperty(isolate, result,
645 factory->value_string(),
646 promise_capability, Just(kDontThrow))
647 .FromJust());
648 break;
649 }
650
651 return *result;
652 }
653
Wake(Handle<JSArrayBuffer> array_buffer,size_t addr,uint32_t num_waiters_to_wake)654 Object FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
655 uint32_t num_waiters_to_wake) {
656 DCHECK_LT(addr, array_buffer->byte_length());
657
658 int waiters_woken = 0;
659 std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
660 auto wait_location = FutexWaitList::ToWaitLocation(backing_store.get(), addr);
661
662 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
663
664 auto& location_lists = g_wait_list.Pointer()->location_lists_;
665 auto it = location_lists.find(wait_location);
666 if (it == location_lists.end()) {
667 return Smi::zero();
668 }
669 FutexWaitListNode* node = it->second.head;
670 while (node && num_waiters_to_wake > 0) {
671 bool delete_this_node = false;
672 std::shared_ptr<BackingStore> node_backing_store =
673 node->backing_store_.lock();
674
675 if (!node->waiting_) {
676 node = node->next_;
677 continue;
678 }
679 // Relying on wait_location_ here is not enough, since we need to guard
680 // against the case where the BackingStore of the node has been deleted and
681 // a new BackingStore recreated in the same memory area.
682 if (backing_store.get() == node_backing_store.get()) {
683 DCHECK_EQ(addr, node->wait_addr_);
684 node->waiting_ = false;
685
686 // Retrieve the next node to iterate before calling NotifyAsyncWaiter,
687 // since NotifyAsyncWaiter will take the node out of the linked list.
688 auto old_node = node;
689 node = node->next_;
690 if (old_node->IsAsync()) {
691 NotifyAsyncWaiter(old_node);
692 } else {
693 // WaitSync will remove the node from the list.
694 old_node->cond_.NotifyOne();
695 }
696 if (num_waiters_to_wake != kWakeAll) {
697 --num_waiters_to_wake;
698 }
699 waiters_woken++;
700 continue;
701 }
702 DCHECK_EQ(nullptr, node_backing_store.get());
703 if (node->async_timeout_time_ == base::TimeTicks()) {
704 // Backing store has been deleted and the node is still waiting, and
705 // there's no timeout. It's never going to be woken up, so we can clean
706 // it up now. We don't need to cancel the timeout task, because there is
707 // none.
708
709 // This cleanup code is not very efficient, since it only kicks in when
710 // a new BackingStore has been created in the same memory area where the
711 // deleted BackingStore was.
712 DCHECK(node->IsAsync());
713 DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
714 delete_this_node = true;
715 }
716 if (node->IsAsync() && node->native_context_.IsEmpty()) {
717 // The NativeContext related to the async waiter has been deleted.
718 // Ditto, clean up now.
719
720 // Using the CancelableTaskManager here is OK since the Isolate is
721 // guaranteed to be alive - FutexEmulation::IsolateDeinit removes all
722 // FutexWaitListNodes owned by an Isolate which is going to die.
723 if (node->CancelTimeoutTask()) {
724 delete_this_node = true;
725 }
726 // If cancelling the timeout task failed, the timeout task is already
727 // running and will clean up the node.
728 }
729
730 if (delete_this_node) {
731 auto old_node = node;
732 node = node->next_;
733 g_wait_list.Pointer()->RemoveNode(old_node);
734 DCHECK_EQ(CancelableTaskManager::kInvalidTaskId,
735 old_node->timeout_task_id_);
736 delete old_node;
737 } else {
738 node = node->next_;
739 }
740 }
741
742 return Smi::FromInt(waiters_woken);
743 }
744
CleanupAsyncWaiterPromise(FutexWaitListNode * node)745 void FutexEmulation::CleanupAsyncWaiterPromise(FutexWaitListNode* node) {
746 // This function must run in the main thread of node's Isolate. This function
747 // may allocate memory. To avoid deadlocks, we shouldn't be holding g_mutex.
748
749 DCHECK(node->IsAsync());
750
751 Isolate* isolate = node->isolate_for_async_waiters_;
752 auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
753
754 if (!node->promise_.IsEmpty()) {
755 Handle<JSPromise> promise = Handle<JSPromise>::cast(
756 Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
757 // Promise keeps the NativeContext alive.
758 DCHECK(!node->native_context_.IsEmpty());
759 Handle<NativeContext> native_context = Handle<NativeContext>::cast(
760 Utils::OpenHandle(*node->native_context_.Get(v8_isolate)));
761
762 // Remove the Promise from the NativeContext's set.
763 Handle<OrderedHashSet> promises(
764 native_context->atomics_waitasync_promises(), isolate);
765 bool was_deleted = OrderedHashSet::Delete(isolate, *promises, *promise);
766 DCHECK(was_deleted);
767 USE(was_deleted);
768 promises = OrderedHashSet::Shrink(isolate, promises);
769 native_context->set_atomics_waitasync_promises(*promises);
770 } else {
771 // NativeContext keeps the Promise alive; if the Promise is dead then
772 // surely NativeContext is too.
773 DCHECK(node->native_context_.IsEmpty());
774 }
775 }
776
ResolveAsyncWaiterPromise(FutexWaitListNode * node)777 void FutexEmulation::ResolveAsyncWaiterPromise(FutexWaitListNode* node) {
778 // This function must run in the main thread of node's Isolate.
779
780 auto v8_isolate =
781 reinterpret_cast<v8::Isolate*>(node->isolate_for_async_waiters_);
782
783 // Try to cancel the timeout task (if one exists). If the timeout task exists,
784 // cancelling it will always succeed. It's not possible for the timeout task
785 // to be running, since it's scheduled to run in the same thread as this task.
786
787 // Using the CancelableTaskManager here is OK since the Isolate is guaranteed
788 // to be alive - FutexEmulation::IsolateDeinit removes all FutexWaitListNodes
789 // owned by an Isolate which is going to die.
790 bool success = node->CancelTimeoutTask();
791 DCHECK(success);
792 USE(success);
793
794 if (!node->promise_.IsEmpty()) {
795 DCHECK(!node->native_context_.IsEmpty());
796 Local<v8::Context> native_context = node->native_context_.Get(v8_isolate);
797 v8::Context::Scope contextScope(native_context);
798 Handle<JSPromise> promise = Handle<JSPromise>::cast(
799 Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
800 Handle<String> result_string;
801 // When waiters are notified, their async_timeout_time_ is reset. Having a
802 // non-zero async_timeout_time_ here means the waiter timed out.
803 if (node->async_timeout_time_ != base::TimeTicks()) {
804 DCHECK(node->waiting_);
805 result_string =
806 node->isolate_for_async_waiters_->factory()->timed_out_string();
807 } else {
808 DCHECK(!node->waiting_);
809 result_string = node->isolate_for_async_waiters_->factory()->ok_string();
810 }
811 MaybeHandle<Object> resolve_result =
812 JSPromise::Resolve(promise, result_string);
813 DCHECK(!resolve_result.is_null());
814 USE(resolve_result);
815 }
816 }
817
ResolveAsyncWaiterPromises(Isolate * isolate)818 void FutexEmulation::ResolveAsyncWaiterPromises(Isolate* isolate) {
819 // This function must run in the main thread of isolate.
820
821 FutexWaitListNode* node;
822 {
823 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
824
825 auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
826 auto it = isolate_map.find(isolate);
827 DCHECK_NE(isolate_map.end(), it);
828
829 node = it->second.head;
830 isolate_map.erase(it);
831 }
832
833 // The list of nodes starting from "node" are no longer on any list, so it's
834 // ok to iterate them without holding the mutex. We also need to not hold the
835 // mutex while calling CleanupAsyncWaiterPromise, since it may allocate
836 // memory.
837 HandleScope handle_scope(isolate);
838 while (node) {
839 DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
840 DCHECK(!node->waiting_);
841 ResolveAsyncWaiterPromise(node);
842 CleanupAsyncWaiterPromise(node);
843 // We've already tried to cancel the timeout task for the node; since we're
844 // now in the same thread the timeout task is supposed to run, we know the
845 // timeout task will never happen, and it's safe to delete the node here.
846 DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
847 node = FutexWaitList::DeleteAsyncWaiterNode(node);
848 }
849 }
850
HandleAsyncWaiterTimeout(FutexWaitListNode * node)851 void FutexEmulation::HandleAsyncWaiterTimeout(FutexWaitListNode* node) {
852 // This function must run in the main thread of node's Isolate.
853 DCHECK(node->IsAsync());
854
855 {
856 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
857
858 node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
859 if (!node->waiting_) {
860 // If the Node is not waiting, it's already scheduled to have its Promise
861 // resolved. Ignore the timeout.
862 return;
863 }
864 g_wait_list.Pointer()->RemoveNode(node);
865 }
866
867 // "node" has been taken out of the lists, so it's ok to access it without
868 // holding the mutex. We also need to not hold the mutex while calling
869 // CleanupAsyncWaiterPromise, since it may allocate memory.
870 HandleScope handle_scope(node->isolate_for_async_waiters_);
871 ResolveAsyncWaiterPromise(node);
872 CleanupAsyncWaiterPromise(node);
873 delete node;
874 }
875
IsolateDeinit(Isolate * isolate)876 void FutexEmulation::IsolateDeinit(Isolate* isolate) {
877 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
878
879 // Iterate all locations to find nodes belonging to "isolate" and delete them.
880 // The Isolate is going away; don't bother cleaning up the Promises in the
881 // NativeContext. Also we don't need to cancel the timeout tasks, since they
882 // will be cancelled by Isolate::Deinit.
883 {
884 auto& location_lists = g_wait_list.Pointer()->location_lists_;
885 auto it = location_lists.begin();
886 while (it != location_lists.end()) {
887 FutexWaitListNode*& head = it->second.head;
888 FutexWaitListNode*& tail = it->second.tail;
889 FutexWaitList::DeleteNodesForIsolate(isolate, &head, &tail);
890 // head and tail are either both nullptr or both non-nullptr.
891 DCHECK_EQ(head == nullptr, tail == nullptr);
892 if (head == nullptr) {
893 location_lists.erase(it++);
894 } else {
895 ++it;
896 }
897 }
898 }
899
900 {
901 auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
902 auto it = isolate_map.find(isolate);
903 if (it != isolate_map.end()) {
904 auto node = it->second.head;
905 while (node) {
906 DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
907 node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
908 node = FutexWaitList::DeleteAsyncWaiterNode(node);
909 }
910 isolate_map.erase(it);
911 }
912 }
913
914 g_wait_list.Pointer()->Verify();
915 }
916
NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,size_t addr)917 Object FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
918 size_t addr) {
919 DCHECK_LT(addr, array_buffer->byte_length());
920 std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
921
922 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
923
924 auto wait_location = FutexWaitList::ToWaitLocation(backing_store.get(), addr);
925 auto& location_lists = g_wait_list.Pointer()->location_lists_;
926 auto it = location_lists.find(wait_location);
927 if (it == location_lists.end()) {
928 return Smi::zero();
929 }
930 int waiters = 0;
931 FutexWaitListNode* node = it->second.head;
932 while (node != nullptr) {
933 std::shared_ptr<BackingStore> node_backing_store =
934 node->backing_store_.lock();
935 if (backing_store.get() == node_backing_store.get() && node->waiting_) {
936 waiters++;
937 }
938
939 node = node->next_;
940 }
941
942 return Smi::FromInt(waiters);
943 }
944
NumAsyncWaitersForTesting(Isolate * isolate)945 Object FutexEmulation::NumAsyncWaitersForTesting(Isolate* isolate) {
946 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
947
948 int waiters = 0;
949 for (const auto& it : g_wait_list.Pointer()->location_lists_) {
950 FutexWaitListNode* node = it.second.head;
951 while (node != nullptr) {
952 if (node->isolate_for_async_waiters_ == isolate && node->waiting_) {
953 waiters++;
954 }
955 node = node->next_;
956 }
957 }
958
959 return Smi::FromInt(waiters);
960 }
961
NumUnresolvedAsyncPromisesForTesting(Handle<JSArrayBuffer> array_buffer,size_t addr)962 Object FutexEmulation::NumUnresolvedAsyncPromisesForTesting(
963 Handle<JSArrayBuffer> array_buffer, size_t addr) {
964 DCHECK_LT(addr, array_buffer->byte_length());
965 std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
966
967 NoGarbageCollectionMutexGuard lock_guard(g_mutex.Pointer());
968
969 int waiters = 0;
970 auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
971 for (const auto& it : isolate_map) {
972 FutexWaitListNode* node = it.second.head;
973 while (node != nullptr) {
974 std::shared_ptr<BackingStore> node_backing_store =
975 node->backing_store_.lock();
976 if (backing_store.get() == node_backing_store.get() &&
977 addr == node->wait_addr_ && !node->waiting_) {
978 waiters++;
979 }
980
981 node = node->next_;
982 }
983 }
984
985 return Smi::FromInt(waiters);
986 }
987
VerifyNode(FutexWaitListNode * node,FutexWaitListNode * head,FutexWaitListNode * tail)988 void FutexWaitList::VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
989 FutexWaitListNode* tail) {
990 #ifdef DEBUG
991 if (node->next_ != nullptr) {
992 DCHECK_NE(node, tail);
993 DCHECK_EQ(node, node->next_->prev_);
994 } else {
995 DCHECK_EQ(node, tail);
996 }
997 if (node->prev_ != nullptr) {
998 DCHECK_NE(node, head);
999 DCHECK_EQ(node, node->prev_->next_);
1000 } else {
1001 DCHECK_EQ(node, head);
1002 }
1003
1004 if (node->async_timeout_time_ != base::TimeTicks()) {
1005 DCHECK(node->IsAsync());
1006 }
1007
1008 DCHECK(NodeIsOnList(node, head));
1009 #endif // DEBUG
1010 }
1011
Verify()1012 void FutexWaitList::Verify() {
1013 #ifdef DEBUG
1014 for (const auto& it : location_lists_) {
1015 FutexWaitListNode* node = it.second.head;
1016 while (node != nullptr) {
1017 VerifyNode(node, it.second.head, it.second.tail);
1018 node = node->next_;
1019 }
1020 }
1021
1022 for (const auto& it : isolate_promises_to_resolve_) {
1023 auto node = it.second.head;
1024 while (node != nullptr) {
1025 VerifyNode(node, it.second.head, it.second.tail);
1026 DCHECK_EQ(it.first, node->isolate_for_async_waiters_);
1027 node = node->next_;
1028 }
1029 }
1030 #endif // DEBUG
1031 }
1032
NodeIsOnList(FutexWaitListNode * node,FutexWaitListNode * head)1033 bool FutexWaitList::NodeIsOnList(FutexWaitListNode* node,
1034 FutexWaitListNode* head) {
1035 auto n = head;
1036 while (n != nullptr) {
1037 if (n == node) {
1038 return true;
1039 }
1040 n = n->next_;
1041 }
1042 return false;
1043 }
1044
1045 } // namespace internal
1046 } // namespace v8
1047