blob: b33a303057318b60c3179b91b4c02772178e7895 [file] [log] [blame]
// Copyright 2015 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.
#ifndef V8_EXECUTION_FUTEX_EMULATION_H_
#define V8_EXECUTION_FUTEX_EMULATION_H_
#include <stdint.h>
#include "include/v8-persistent-handle.h"
#include "src/base/atomicops.h"
#include "src/base/macros.h"
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/time.h"
#include "src/tasks/cancelable-task.h"
#include "src/utils/allocation.h"
// Support for emulating futexes, a low-level synchronization primitive. They
// are natively supported by Linux, but must be emulated for other platforms.
// This library emulates them on all platforms using mutexes and condition
// variables for consistency.
//
// This is used by the Futex API defined in the SharedArrayBuffer draft spec,
// found here: https://github.com/tc39/ecmascript_sharedmem
namespace v8 {
class Promise;
namespace base {
class TimeDelta;
} // namespace base
namespace internal {
class BackingStore;
class FutexWaitList;
class Isolate;
class JSArrayBuffer;
class AtomicsWaitWakeHandle {
public:
explicit AtomicsWaitWakeHandle(Isolate* isolate) : isolate_(isolate) {}
void Wake();
inline bool has_stopped() const { return stopped_; }
private:
Isolate* isolate_;
bool stopped_ = false;
};
class FutexWaitListNode {
public:
// Create a sync FutexWaitListNode.
FutexWaitListNode() = default;
// Create an async FutexWaitListNode.
FutexWaitListNode(std::weak_ptr<BackingStore> backing_store,
void* wait_location, Handle<JSObject> promise_capability,
Isolate* isolate);
// Disallow copying nodes.
FutexWaitListNode(const FutexWaitListNode&) = delete;
FutexWaitListNode& operator=(const FutexWaitListNode&) = delete;
void NotifyWake();
bool IsAsync() const { return async_state_ != nullptr; }
// Returns false if the cancelling failed, true otherwise.
bool CancelTimeoutTask();
private:
friend class FutexEmulation;
friend class FutexWaitList;
// Async wait requires substantially more information than synchronous wait.
// Hence store that additional information in a heap-allocated struct to make
// it more obvious that this will only be needed for the async case.
struct AsyncState {
AsyncState(Isolate* isolate, std::shared_ptr<TaskRunner> task_runner,
std::weak_ptr<BackingStore> backing_store,
v8::Global<v8::Promise> promise,
v8::Global<v8::Context> native_context)
: isolate_for_async_waiters(isolate),
task_runner(std::move(task_runner)),
backing_store(std::move(backing_store)),
promise(std::move(promise)),
native_context(std::move(native_context)) {
DCHECK(this->promise.IsWeak());
DCHECK(this->native_context.IsWeak());
}
~AsyncState() {
// Assert that the timeout task was cancelled.
DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, timeout_task_id);
}
Isolate* const isolate_for_async_waiters;
std::shared_ptr<TaskRunner> const task_runner;
// The backing store on which we are waiting might die in an async wait.
// We keep a weak_ptr to verify during a wake operation that the original
// backing store is still mapped to that address.
std::weak_ptr<BackingStore> const backing_store;
// Weak Global handle. Must not be synchronously resolved by a non-owner
// Isolate.
v8::Global<v8::Promise> const promise;
// Weak Global handle.
v8::Global<v8::Context> const native_context;
// If timeout_time_ is base::TimeTicks(), this async waiter doesn't have a
// timeout or has already been notified. Values other than base::TimeTicks()
// are used for async waiters with an active timeout.
base::TimeTicks timeout_time;
// The task ID of the timeout task.
CancelableTaskManager::Id timeout_task_id =
CancelableTaskManager::kInvalidTaskId;
};
base::ConditionVariable cond_;
// prev_ and next_ are protected by FutexEmulationGlobalState::mutex.
FutexWaitListNode* prev_ = nullptr;
FutexWaitListNode* next_ = nullptr;
// The memory location the FutexWaitListNode is waiting on. Equals
// backing_store_->buffer_start() + wait_addr at FutexWaitListNode creation
// time. This address is used find the node in the per-location list, or to
// remove it.
// Note that during an async wait the BackingStore might get deleted while
// this node is alive.
void* wait_location_ = nullptr;
// waiting_ and interrupted_ are protected by FutexEmulationGlobalState::mutex
// if this node is currently contained in FutexEmulationGlobalState::wait_list
// or an AtomicsWaitWakeHandle has access to it.
bool waiting_ = false;
bool interrupted_ = false;
// State used for an async wait; nullptr on sync waits.
const std::unique_ptr<AsyncState> async_state_;
};
class FutexEmulation : public AllStatic {
public:
enum WaitMode { kSync = 0, kAsync };
enum class CallType { kIsNotWasm = 0, kIsWasm };
// Pass to Wake() to wake all waiters.
static const uint32_t kWakeAll = UINT32_MAX;
// Check that array_buffer[addr] == value, and return "not-equal" if not. If
// they are equal, block execution on |isolate|'s thread until woken via
// |Wake|, or when the time given in |rel_timeout_ms| elapses. Note that
// |rel_timeout_ms| can be Infinity.
// If woken, return "ok", otherwise return "timed-out". The initial check and
// the decision to wait happen atomically.
static Tagged<Object> WaitJs32(Isolate* isolate, WaitMode mode,
Handle<JSArrayBuffer> array_buffer,
size_t addr, int32_t value,
double rel_timeout_ms);
// An version of WaitJs32 for int64_t values.
static Tagged<Object> WaitJs64(Isolate* isolate, WaitMode mode,
Handle<JSArrayBuffer> array_buffer,
size_t addr, int64_t value,
double rel_timeout_ms);
// Same as WaitJs above except it returns 0 (ok), 1 (not equal) and 2 (timed
// out) as expected by Wasm.
V8_EXPORT_PRIVATE static Tagged<Object> WaitWasm32(
Isolate* isolate, Handle<JSArrayBuffer> array_buffer, size_t addr,
int32_t value, int64_t rel_timeout_ns);
// Same as Wait32 above except it checks for an int64_t value in the
// array_buffer.
V8_EXPORT_PRIVATE static Tagged<Object> WaitWasm64(
Isolate* isolate, Handle<JSArrayBuffer> array_buffer, size_t addr,
int64_t value, int64_t rel_timeout_ns);
// Wake |num_waiters_to_wake| threads that are waiting on the given |addr|.
// |num_waiters_to_wake| can be kWakeAll, in which case all waiters are
// woken. The rest of the waiters will continue to wait. The return value is
// the number of woken waiters.
// Variant 1: Compute the wait address from the |array_buffer| and |addr|.
V8_EXPORT_PRIVATE static int Wake(Tagged<JSArrayBuffer> array_buffer,
size_t addr, uint32_t num_waiters_to_wake);
// Variant 2: Pass raw |addr| (used for WebAssembly atomic.notify).
static int Wake(void* addr, uint32_t num_waiters_to_wake);
// Called before |isolate| dies. Removes async waiters owned by |isolate|.
static void IsolateDeinit(Isolate* isolate);
// Return the number of threads or async waiters waiting on |addr|. Should
// only be used for testing.
static int NumWaitersForTesting(Tagged<JSArrayBuffer> array_buffer,
size_t addr);
// Return the number of async waiters (which belong to |isolate|) waiting.
// Should only be used for testing.
static int NumAsyncWaitersForTesting(Isolate* isolate);
// Return the number of async waiters which were waiting for |addr| and are
// now waiting for the Promises to be resolved. Should only be used for
// testing.
static int NumUnresolvedAsyncPromisesForTesting(
Tagged<JSArrayBuffer> array_buffer, size_t addr);
private:
friend class FutexWaitListNode;
friend class AtomicsWaitWakeHandle;
friend class ResolveAsyncWaiterPromisesTask;
friend class AsyncWaiterTimeoutTask;
template <typename T>
static Tagged<Object> Wait(Isolate* isolate, WaitMode mode,
Handle<JSArrayBuffer> array_buffer, size_t addr,
T value, double rel_timeout_ms);
template <typename T>
static Tagged<Object> 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 = CallType::kIsNotWasm);
template <typename T>
static Tagged<Object> WaitSync(Isolate* isolate,
Handle<JSArrayBuffer> array_buffer,
size_t addr, T value, bool use_timeout,
int64_t rel_timeout_ns, CallType call_type);
template <typename T>
static Tagged<Object> WaitAsync(Isolate* isolate,
Handle<JSArrayBuffer> array_buffer,
size_t addr, T value, bool use_timeout,
int64_t rel_timeout_ns, CallType call_type);
// Resolve the Promises of the async waiters which belong to |isolate|.
static void ResolveAsyncWaiterPromises(Isolate* isolate);
static void ResolveAsyncWaiterPromise(FutexWaitListNode* node);
static void HandleAsyncWaiterTimeout(FutexWaitListNode* node);
static void NotifyAsyncWaiter(FutexWaitListNode* node);
// Remove the node's Promise from the NativeContext's Promise set.
static void CleanupAsyncWaiterPromise(FutexWaitListNode* node);
};
} // namespace internal
} // namespace v8
#endif // V8_EXECUTION_FUTEX_EMULATION_H_