Based on the Rust implementations: * https://github.com/rust-lang/rust/blob/2aabf3ce0569dd7c8c2d2a27944850056dcf2566/library/std/src/sys/sync/mutex/mod.rs#L3 * https://github.com/rust-lang/rust/blob/2aabf3ce0569dd7c8c2d2a27944850056dcf2566/library/std/src/sys/sync/mutex/futex.rs * https://github.com/rust-lang/rust/blob/2aabf3ce0569dd7c8c2d2a27944850056dcf2566/library/std/src/sys/sync/condvar/futex.rs This is a ~0.4% speedometer improvement on Windows (Linux and Android measurements in progress). This also adds a Linux implementation that will be enabled in a separate bug if worth it. Differential Revision: https://phabricator.services.mozilla.com/D300059
99 lines
3.1 KiB
C++
99 lines
3.1 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/PlatformMutex.h"
|
|
|
|
#include <cstdint>
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/Futex.h"
|
|
#include "mozilla/Atomics.h"
|
|
|
|
namespace {
|
|
|
|
using State = mozilla::SmallFutex::ValueType;
|
|
|
|
constexpr State UNLOCKED = 0;
|
|
constexpr State LOCKED = 1; // locked, no other threads waiting.
|
|
constexpr State CONTENDED = 2; // locked, and other threads waiting.
|
|
|
|
// We only use `load` (and not `swap` or `compare_exchange`) while spinning, to
|
|
// be easier on the caches.
|
|
State Spin(mozilla::SmallFutex& aFutex) {
|
|
uint32_t spinCount = 100;
|
|
while (true) {
|
|
State state = aFutex.mValue.load(std::memory_order_relaxed);
|
|
// We stop spinning when the mutex is UNLOCKED, but also when it's
|
|
// CONTENDED.
|
|
if (state != LOCKED || spinCount == 0) {
|
|
return state;
|
|
}
|
|
mozilla::cpu_pause();
|
|
--spinCount;
|
|
}
|
|
}
|
|
|
|
MOZ_COLD void LockContended(mozilla::SmallFutex& aFutex) {
|
|
// Spin first to speed things up if the lock is released quickly.
|
|
State state = Spin(aFutex);
|
|
|
|
// If it's unlocked now, attempt to take the lock without marking it as
|
|
// contended.
|
|
if (state == UNLOCKED && aFutex.mValue.compare_exchange_strong(
|
|
state, LOCKED, std::memory_order_acquire,
|
|
std::memory_order_relaxed)) {
|
|
return;
|
|
}
|
|
|
|
while (true) {
|
|
// Put the lock in contended state. We avoid an unnecessary write if it is
|
|
// already CONTENDED, to be friendlier for the caches.
|
|
if (state != CONTENDED &&
|
|
aFutex.mValue.exchange(CONTENDED, std::memory_order_acquire) ==
|
|
UNLOCKED) {
|
|
// We changed it from UNLOCKED to CONTENDED, so we just successfully
|
|
// locked it.
|
|
return;
|
|
}
|
|
|
|
// Wait for the futex to change state, assuming it is still CONTENDED.
|
|
(void)aFutex.wait(CONTENDED);
|
|
|
|
// Spin again after waking up.
|
|
state = Spin(aFutex);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
mozilla::detail::MutexImpl::MutexImpl() = default;
|
|
mozilla::detail::MutexImpl::~MutexImpl() = default;
|
|
|
|
void mozilla::detail::MutexImpl::lock() {
|
|
State expected = UNLOCKED;
|
|
if (!mFutex.mValue.compare_exchange_strong(expected, LOCKED,
|
|
std::memory_order_acquire,
|
|
std::memory_order_relaxed)) {
|
|
LockContended(mFutex);
|
|
}
|
|
}
|
|
|
|
bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); }
|
|
|
|
bool mozilla::detail::MutexImpl::mutexTryLock() {
|
|
State expected = UNLOCKED;
|
|
return mFutex.mValue.compare_exchange_strong(
|
|
expected, LOCKED, std::memory_order_acquire, std::memory_order_relaxed);
|
|
}
|
|
|
|
void mozilla::detail::MutexImpl::unlock() {
|
|
if (mFutex.mValue.exchange(UNLOCKED, std::memory_order_release) ==
|
|
CONTENDED) {
|
|
// We only wake up one thread. When that thread locks the mutex, it will
|
|
// mark the mutex as CONTENDED (see LockContended above), which makes sure
|
|
// that any other waiting threads will also be woken up eventually.
|
|
mFutex.wake();
|
|
}
|
|
}
|