blob: 5d3c30cb93bed4466476fc8890ec90a56c85c1ff [file] [log] [blame]
//
// Copyright 2024 The ANGLE 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.
//
// SimpleMutex.h:
// A simple non-recursive mutex that only supports lock and unlock operations. As such, it can be
// implemented more efficiently than a generic mutex such as std::mutex. In the uncontended
// paths, the implementation boils down to basically an inlined atomic operation and an untaken
// branch. The implementation in this file is inspired by Mesa's src/util/simple_mtx.h, which in
// turn is based on "mutex3" in:
//
// "Futexes Are Tricky"
// http://www.akkadia.org/drepper/futex.pdf
//
// Given that std::condition_variable only interacts with std::mutex, SimpleMutex cannot be used
// with condition variables.
//
#ifndef COMMON_SIMPLEMUTEX_H_
#define COMMON_SIMPLEMUTEX_H_
#include "common/log_utils.h"
#include "common/platform.h"
#include <atomic>
#include <mutex>
// Enable futexes on:
//
// - Linux and derivatives (Android, ChromeOS, etc)
// - Windows 8+
//
// There is no TSAN support for futex currently, so it is disabled in that case
#if !defined(ANGLE_WITH_TSAN)
# if defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID)
// Linux has had futexes for a very long time. Assume support.
# define ANGLE_USE_FUTEX 1
# elif defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_ENABLE_WINDOWS_UWP) && \
!defined(ANGLE_WINDOWS_NO_FUTEX)
// Windows has futexes since version 8, which is already end of life (let alone older versions).
// Assume support.
# define ANGLE_USE_FUTEX 1
# endif // defined(ANGLE_PLATFORM_LINUX) || defined(ANGLE_PLATFORM_ANDROID)
#endif // !defined(ANGLE_WITH_TSAN)
namespace angle
{
namespace priv
{
#if ANGLE_USE_FUTEX
class MutexOnFutex
{
public:
void lock()
{
uint32_t oldState = kUnlocked;
const bool lockTaken = mState.compare_exchange_strong(oldState, kLocked);
// In uncontended cases, the lock is acquired and there's nothing to do
if (ANGLE_UNLIKELY(!lockTaken))
{
ASSERT(oldState == kLocked || oldState == kBlocked);
// If not already marked as such, signal that the mutex is contended.
if (oldState != kBlocked)
{
oldState = mState.exchange(kBlocked, std::memory_order_acq_rel);
}
// Wait until the lock is acquired
while (oldState != kUnlocked)
{
futexWait();
oldState = mState.exchange(kBlocked, std::memory_order_acq_rel);
}
}
}
void unlock()
{
// Unlock the mutex
const uint32_t oldState = mState.fetch_add(-1, std::memory_order_acq_rel);
// If another thread is waiting on this mutex, wake it up
if (ANGLE_UNLIKELY(oldState != kLocked))
{
mState.store(kUnlocked, std::memory_order_relaxed);
futexWake();
}
}
void assertLocked() { ASSERT(mState.load(std::memory_order_relaxed) != kUnlocked); }
private:
void futexWait();
void futexWake();
// Note: the ordering of these values is important due to |unlock()|'s atomic decrement.
static constexpr uint32_t kUnlocked = 0;
static constexpr uint32_t kLocked = 1;
static constexpr uint32_t kBlocked = 2;
std::atomic_uint32_t mState = 0;
};
#else // !ANGLE_USE_FUTEX
class MutexOnStd
{
public:
void lock() { mutex.lock(); }
void unlock() { mutex.unlock(); }
void assertLocked() { ASSERT(isLocked()); }
private:
bool isLocked()
{
// This works because angle::SimpleMutex does not support recursion
const bool acquiredLock = mutex.try_lock();
if (acquiredLock)
{
mutex.unlock();
}
return !acquiredLock;
}
std::mutex mutex;
};
#endif // ANGLE_USE_FUTEX
} // namespace priv
#if ANGLE_USE_FUTEX
using SimpleMutex = priv::MutexOnFutex;
#else
using SimpleMutex = priv::MutexOnStd;
#endif
} // namespace angle
#endif // COMMON_SIMPLEMUTEX_H_