#include "src/objects/js-atomics-synchronization.h"
#include "src/base/macros.h"
#include "src/base/platform/yield-processor.h"
#include "src/execution/isolate-inl.h"
#include "src/objects/js-atomics-synchronization-inl.h"
#include "src/objects/js-promise-inl.h"
#include "src/objects/waiter-queue-node.h"
#include "src/sandbox/external-pointer-inl.h"
namespace v8 {
namespace internal {
namespace detail {
class WaiterQueueNode;
}
namespace {
}
namespace detail {
class V8_NODISCARD WaiterQueueLockGuard final {
using StateT = JSSynchronizationPrimitive::StateT;
public:
explicit WaiterQueueLockGuard(std::atomic<StateT>* state,
StateT& current_state)
: state_(state), new_state_(kInvalidState) {
while (!JSSynchronizationPrimitive::TryLockWaiterQueueExplicit(
state, current_state)) {
YIELD_PROCESSOR;
}
}
explicit WaiterQueueLockGuard(std::atomic<StateT>* state, bool is_locked)
: state_(state), new_state_(kInvalidState) {
CHECK(is_locked);
DCHECK(JSSynchronizationPrimitive::IsWaiterQueueLockedField::decode(
state->load()));
}
WaiterQueueLockGuard(const WaiterQueueLockGuard&) = delete;
WaiterQueueLockGuard() = delete;
~WaiterQueueLockGuard() {
DCHECK_NOT_NULL(state_);
DCHECK_NE(new_state_, kInvalidState);
DCHECK(JSSynchronizationPrimitive::IsWaiterQueueLockedField::decode(
state_->load()));
new_state_ = JSSynchronizationPrimitive::IsWaiterQueueLockedField::update(
new_state_, false);
state_->store(new_state_, std::memory_order_release);
}
void set_new_state(StateT new_state) { new_state_ = new_state; }
static std::optional<WaiterQueueLockGuard>
NewAlreadyLockedWaiterQueueLockGuard(std::atomic<StateT>* state) {
return std::optional<WaiterQueueLockGuard>(std::in_place, state, true);
}
private:
static constexpr StateT kInvalidState =
~JSSynchronizationPrimitive::kEmptyState;
std::atomic<StateT>* state_;
StateT new_state_;
};
}
bool JSSynchronizationPrimitive::TryLockWaiterQueueExplicit(
std::atomic<StateT>* state, StateT& expected) {
expected = IsWaiterQueueLockedField::update(expected, false);
return state->compare_exchange_weak(
expected, IsWaiterQueueLockedField::update(expected, true),
std::memory_order_acquire, std::memory_order_relaxed);
}
void JSSynchronizationPrimitive::SetWaiterQueueStateOnly(
std::atomic<StateT>* state, StateT new_state) {
DCHECK_EQ(new_state & ~kWaiterQueueMask, 0);
StateT expected = state->load(std::memory_order_relaxed);
StateT desired;
do {
desired = new_state | (expected & ~kWaiterQueueMask);
} while (!state->compare_exchange_weak(
expected, desired, std::memory_order_release, std::memory_order_relaxed));
}
Tagged<Object> JSSynchronizationPrimitive::NumWaitersForTesting(
Isolate* requester) {
DisallowGarbageCollection no_gc;
std::atomic<StateT>* state = AtomicStatePtr();
StateT current_state = state->load(std::memory_order_acquire);
if (!HasWaitersField::decode(current_state)) return Smi::FromInt(0);
int num_waiters;
{
while (!TryLockWaiterQueueExplicit(state, current_state)) {
YIELD_PROCESSOR;
}
if (!HasWaitersField::decode(current_state)) {
SetWaiterQueueStateOnly(state, kEmptyState);
return Smi::FromInt(0);
}
WaiterQueueNode* waiter_head = DestructivelyGetWaiterQueueHead(requester);
DCHECK_NOT_NULL(waiter_head);
num_waiters = WaiterQueueNode::LengthFromHead(waiter_head);
DCHECK_EQ(state->load(),
IsWaiterQueueLockedField::update(current_state, true));
StateT new_state = SetWaiterQueueHead(requester, waiter_head, kEmptyState);
new_state = IsWaiterQueueLockedField::update(new_state, false);
SetWaiterQueueStateOnly(state, new_state);
}
return Smi::FromInt(num_waiters);
}
DirectHandle<JSObject> JSAtomicsMutex::CreateResultObject(
Isolate* isolate, DirectHandle<Object> value, bool success) {
DirectHandle<JSObject> result =
isolate->factory()->NewJSObject(isolate->object_function());
DirectHandle<Object> success_value = isolate->factory()->ToBoolean(success);
JSObject::AddProperty(isolate, result, "value", value,
PropertyAttributes::NONE);
JSObject::AddProperty(isolate, result, "success", success_value,
PropertyAttributes::NONE);
return result;
}
bool JSAtomicsMutex::TryLockExplicit(std::atomic<StateT>* state,
StateT& expected) {
expected = IsLockedField::update(expected, false);
return state->compare_exchange_weak(
expected, IsLockedField::update(expected, true),
std::memory_order_acquire, std::memory_order_relaxed);
}
bool JSAtomicsMutex::BackoffTryLock(Isolate* requester,
DirectHandle<JSAtomicsMutex> mutex,
std::atomic<StateT>* state) {
constexpr int kSpinCount = 64;
constexpr int kMaxBackoff = 16;
int tries = 0;
int backoff = 1;
StateT current_state = state->load(std::memory_order_relaxed);
do {
if (JSAtomicsMutex::TryLockExplicit(state, current_state)) return true;
for (int yields = 0; yields < backoff; yields++) {
YIELD_PROCESSOR;
tries++;
}
backoff = std::min(kMaxBackoff, backoff << 1);
} while (tries < kSpinCount);
return false;
}
bool JSAtomicsMutex::MaybeEnqueueNode(Isolate* requester,
DirectHandle<JSAtomicsMutex> mutex,
std::atomic<StateT>* state,
WaiterQueueNode* this_waiter) {
DCHECK_NOT_NULL(this_waiter);
StateT current_state = state->load(std::memory_order_relaxed);
std::optional<WaiterQueueLockGuard> waiter_queue_lock_guard =
LockWaiterQueueOrJSMutex(state, current_state);
if (!waiter_queue_lock_guard.has_value()) {
DCHECK(IsLockedField::decode(state->load()));
return false;
}
WaiterQueueNode* waiter_head =
mutex->DestructivelyGetWaiterQueueHead(requester);
WaiterQueueNode::Enqueue(&waiter_head, this_waiter);
DCHECK_EQ(state->load(),
IsWaiterQueueLockedField::update(current_state, true));
StateT new_state =
mutex->SetWaiterQueueHead(requester, waiter_head, current_state);
DCHECK(IsLockedField::decode(current_state));
new_state = IsLockedField::update(new_state, true);
waiter_queue_lock_guard->set_new_state(new_state);
return true;
}
std::optional<WaiterQueueLockGuard> JSAtomicsMutex::LockWaiterQueueOrJSMutex(
std::atomic<StateT>* state, StateT& current_state) {
for (;;) {
if (IsLockedField::decode(current_state) &&
TryLockWaiterQueueExplicit(state, current_state)) {
return WaiterQueueLockGuard::NewAlreadyLockedWaiterQueueLockGuard(state);
}
if (TryLockExplicit(state, current_state)) return std::nullopt;
YIELD_PROCESSOR;
}
}
bool JSAtomicsMutex::LockJSMutexOrDequeueTimedOutWaiter(
Isolate* requester, std::atomic<StateT>* state,
WaiterQueueNode* timed_out_waiter) {
StateT current_state = state->load(std::memory_order_relaxed);
if (!HasWaitersField::decode(current_state)) return false;
while (!TryLockWaiterQueueExplicit(state, current_state)) {
YIELD_PROCESSOR;
}
WaiterQueueNode* waiter_head = DestructivelyGetWaiterQueueHead(requester);
if (waiter_head == nullptr) {
DCHECK(!HasWaitersField::decode(current_state));
SetWaiterQueueStateOnly(state, kUnlockedUncontended);
return false;
}
WaiterQueueNode* dequeued_node = WaiterQueueNode::DequeueMatching(
&waiter_head,
[&](WaiterQueueNode* node) { return node == timed_out_waiter; });
DCHECK_EQ(state->load(),
IsWaiterQueueLockedField::update(current_state, true));
StateT new_state = kUnlockedUncontended;
new_state = SetWaiterQueueHead(requester, waiter_head, new_state);
if (!dequeued_node) {
new_state = IsLockedField::update(new_state, true);
DCHECK(!IsWaiterQueueLockedField::decode(new_state));
current_state = IsLockedField::update(current_state, false);
if (state->compare_exchange_strong(current_state, new_state,
std::memory_order_acq_rel,
std::memory_order_relaxed)) {
return true;
}
DCHECK(IsLockedField::decode(state->load()));
state->store(new_state, std::memory_order_release);
return false;
}
SetWaiterQueueStateOnly(state, new_state);
return false;
}
bool JSAtomicsMutex::LockSlowPath(Isolate* requester,
DirectHandle<JSAtomicsMutex> mutex,
std::atomic<StateT>* state,
std::optional<base::TimeDelta> timeout) {
for (;;) {
if (BackoffTryLock(requester, mutex, state)) return true;
WaiterQueueNode this_waiter(requester);
if (!MaybeEnqueueNode(requester, mutex, state, &this_waiter)) return true;
bool rv;
if (timeout) {
rv = this_waiter.WaitFor(*timeout);
state = mutex->AtomicStatePtr();
if (!rv) {
rv = mutex->LockJSMutexOrDequeueTimedOutWaiter(requester, state,
&this_waiter);
return rv;
}
} else {
this_waiter.Wait();
state = mutex->AtomicStatePtr();
}
}
}
void JSAtomicsMutex::UnlockSlowPath(Isolate* requester,
std::atomic<StateT>* state) {
DCHECK_EQ(ThreadId::Invalid().ToInteger(),
AtomicOwnerThreadIdPtr()->load(std::memory_order_relaxed));
StateT current_state = state->load(std::memory_order_relaxed);
WaiterQueueLockGuard waiter_queue_lock_guard(state, current_state);
if (!HasWaitersField::decode(current_state)) {
StateT new_state = IsLockedField::update(current_state, false);
waiter_queue_lock_guard.set_new_state(new_state);
return;
}
WaiterQueueNode* waiter_head = DestructivelyGetWaiterQueueHead(requester);
DCHECK_NOT_NULL(waiter_head);
WaiterQueueNode* old_head = WaiterQueueNode::Dequeue(&waiter_head);
StateT new_state = IsLockedField::update(current_state, false);
new_state = SetWaiterQueueHead(requester, waiter_head, new_state);
waiter_queue_lock_guard.set_new_state(new_state);
old_head->Notify();
}
void JSAtomicsCondition::QueueWaiter(Isolate* requester,
DirectHandle<JSAtomicsCondition> cv,
WaiterQueueNode* waiter) {
std::atomic<StateT>* state = cv->AtomicStatePtr();
StateT current_state = state->load(std::memory_order_relaxed);
WaiterQueueLockGuard waiter_queue_lock_guard(state, current_state);
WaiterQueueNode* waiter_head = cv->DestructivelyGetWaiterQueueHead(requester);
WaiterQueueNode::Enqueue(&waiter_head, waiter);
DCHECK_EQ(state->load(),
IsWaiterQueueLockedField::update(current_state, true));
StateT new_state =
cv->SetWaiterQueueHead(requester, waiter_head, current_state);
waiter_queue_lock_guard.set_new_state(new_state);
}
bool JSAtomicsCondition::WaitFor(Isolate* requester,
DirectHandle<JSAtomicsCondition> cv,
DirectHandle<JSAtomicsMutex> mutex,
std::optional<base::TimeDelta> timeout) {
DisallowGarbageCollection no_gc;
bool rv;
{
WaiterQueueNode this_waiter(requester);
JSAtomicsCondition::QueueWaiter(requester, cv, &this_waiter);
mutex->Unlock(requester);
if (timeout) {
rv = this_waiter.WaitFor(*timeout);
if (!rv) {
std::atomic<StateT>* state = cv->AtomicStatePtr();
DequeueExplicit(
requester, cv, state, [&](WaiterQueueNode** waiter_head) {
WaiterQueueNode* dequeued = WaiterQueueNode::DequeueMatching(
waiter_head,
[&](WaiterQueueNode* node) { return node == &this_waiter; });
return dequeued ? 1 : 0;
});
}
} else {
this_waiter.Wait();
rv = true;
}
}
JSAtomicsMutex::Lock(requester, mutex);
return rv;
}
uint32_t JSAtomicsCondition::DequeueExplicit(
Isolate* requester, DirectHandle<JSAtomicsCondition> cv,
std::atomic<StateT>* state, const DequeueAction& action_under_lock) {
StateT current_state = state->load(std::memory_order_relaxed);
if (!HasWaitersField::decode(current_state)) return 0;
WaiterQueueLockGuard waiter_queue_lock_guard(state, current_state);
WaiterQueueNode* waiter_head = cv->DestructivelyGetWaiterQueueHead(requester);
if (waiter_head == nullptr) {
StateT new_state = kEmptyState;
waiter_queue_lock_guard.set_new_state(new_state);
return 0;
}
uint32_t num_dequeued_waiters = action_under_lock(&waiter_head);
DCHECK_EQ(state->load(),
IsWaiterQueueLockedField::update(current_state, true));
StateT new_state =
cv->SetWaiterQueueHead(requester, waiter_head, current_state);
waiter_queue_lock_guard.set_new_state(new_state);
return num_dequeued_waiters;
}
uint32_t JSAtomicsCondition::Notify(Isolate* requester,
DirectHandle<JSAtomicsCondition> cv,
uint32_t count) {
std::atomic<StateT>* state = cv->AtomicStatePtr();
return DequeueExplicit(
requester, cv, state, [=](WaiterQueueNode** waiter_head) -> uint32_t {
WaiterQueueNode* old_head;
if (count == 1) {
old_head = WaiterQueueNode::Dequeue(waiter_head);
if (!old_head) return 0;
old_head->Notify();
return 1;
}
if (count == kAllWaiters) {
old_head = *waiter_head;
*waiter_head = nullptr;
} else {
old_head = WaiterQueueNode::Split(waiter_head, count);
}
if (!old_head) return 0;
return old_head->NotifyAllInList();
});
}
}
}