Hash :
49317d73
Author :
Date :
2024-05-10T16:11:59
Prepare resource map for being potentially thread-safe Resource maps are used for both shared and context-private resources. Context-private resource maps do not need any locking. Once glBind* and similar commands are made free of the share group lock, the resource map of the corresponding type must be made thread safe, especially for look up. This change adds the ability to the resource map to be either thread safe or not, based on the type of the resource it contains. Currently, only a test type (unsigned int) is thread safe (used in unit tests). This is achieved by a combination of the following: - For resource maps that need a lock, the flat part of the map has an initially reasonable size, but is _never_ reallocated. This makes access to that part of the map lockfree. - The hash-map that contains large ids is always protected by a mutex. Follow up changes will start enabling thread-safety for resources as their corresponding glBind command is made lockless. Bug: angleproject:8667 Change-Id: Ia4ffffee41f021d833d31f296bc883bf12f1135f Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/5527771 Reviewed-by: Geoff Lang <geofflang@chromium.org> Reviewed-by: Charlie Lao <cclao@google.com> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
//
// 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
// A no-op mutex to replace SimpleMutex where a lock is not needed.
struct NoOpMutex
{
void lock() {}
void unlock() {}
bool try_lock() { return true; }
};
} // namespace angle
#endif // COMMON_SIMPLEMUTEX_H_