/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2015 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #ifndef XENIA_BASE_MUTEX_H_ #define XENIA_BASE_MUTEX_H_ #include #include #include #include "platform.h" #if XE_PLATFORM_WIN32 #include "platform_win.h" #else #include #endif #include "memory.h" #define XE_ENABLE_FAST_WIN32_MUTEX 1 #define XE_ENABLE_FAST_LINUX_MUTEX 1 namespace xe { #if XE_PLATFORM_WIN32 == 1 && XE_ENABLE_FAST_WIN32_MUTEX == 1 // Recursive mutex using SRWLOCK. class alignas(4096) xe_global_mutex { SRWLOCK srwlock_ = SRWLOCK_INIT; DWORD owner_thread_ = 0; uint32_t recursion_count_ = 0; public: xe_global_mutex() = default; ~xe_global_mutex() = default; void lock(); void unlock(); bool try_lock(); }; using global_mutex_type = xe_global_mutex; // Non-recursive mutex using SRWLOCK. class alignas(64) xe_fast_mutex { SRWLOCK srwlock_ = SRWLOCK_INIT; DWORD owner_thread_ = 0; public: xe_fast_mutex() = default; ~xe_fast_mutex() = default; void lock(); void unlock(); bool try_lock(); }; // a mutex that is extremely unlikely to ever be locked // use for race conditions that have extremely remote odds of happening class xe_unlikely_mutex { std::atomic mut; bool _tryget() { uint32_t lock_expected = 0; return mut.compare_exchange_strong(lock_expected, 1); } public: xe_unlikely_mutex() : mut(0) {} ~xe_unlikely_mutex() { mut = 0; } void lock() { if (XE_LIKELY(_tryget())) { return; } else { do { // chrispy: warning, if no SMT, mm_pause does nothing... #if XE_ARCH_AMD64 == 1 _mm_pause(); #endif } while (!_tryget()); } } void unlock() { mut.exchange(0); } bool try_lock() { return _tryget(); } }; using xe_mutex = xe_fast_mutex; #elif XE_PLATFORM_LINUX == 1 && XE_ENABLE_FAST_LINUX_MUTEX == 1 #define XE_LINUX_MUTEX_SPINCOUNT 128 // Fast recursive mutex for Linux using futex // Mimics Windows CRITICAL_SECTION behavior: spin before blocking class alignas(4096) xe_global_mutex { std::atomic state_{0}; // 0 = unlocked, 1 = locked, 2 = contended std::atomic owner_{0}; uint32_t recursion_count_{0}; void lock_slow(); public: xe_global_mutex() = default; ~xe_global_mutex() = default; void lock(); void unlock(); bool try_lock(); }; using global_mutex_type = xe_global_mutex; // Fast non-recursive mutex for Linux using futex class alignas(64) xe_fast_mutex { std::atomic state_{0}; // 0 = unlocked, 1 = locked, 2 = contended void lock_slow(); public: xe_fast_mutex() = default; ~xe_fast_mutex() = default; void lock(); void unlock(); bool try_lock(); }; // xe_unlikely_mutex remains a simple spinlock for Linux too class xe_unlikely_mutex { std::atomic mut{0}; bool _tryget() { uint32_t lock_expected = 0; return mut.compare_exchange_strong( lock_expected, 1, std::memory_order_acquire, std::memory_order_relaxed); } public: xe_unlikely_mutex() = default; ~xe_unlikely_mutex() = default; void lock() { if (XE_LIKELY(_tryget())) { return; } // Spin a bit before yielding for (int i = 0; i < XE_LINUX_MUTEX_SPINCOUNT; ++i) { #if XE_ARCH_AMD64 == 1 _mm_pause(); #endif if (_tryget()) return; } // Fall back to yielding while (!_tryget()) { std::this_thread::yield(); } } void unlock() { mut.store(0, std::memory_order_release); } bool try_lock() { return _tryget(); } }; using xe_mutex = xe_fast_mutex; #else using global_mutex_type = std::recursive_mutex; using xe_mutex = std::mutex; using xe_unlikely_mutex = std::mutex; #endif struct null_mutex { public: static void lock() {} static void unlock() {} static bool try_lock() { return true; } }; using global_unique_lock_type = std::unique_lock; // The global critical region mutex singleton. // This must guard any operation that may suspend threads or be sensitive to // being suspended such as global table locks and such. // To prevent deadlocks this should be the first lock acquired and be held // for the entire duration of the critical region (longer than any other lock). // // As a general rule if some code can only be accessed from the guest you can // guard it with only the global critical region and be assured nothing else // will touch it. If it will be accessed from non-guest threads you may need // some additional protection. // // You can think of this as disabling interrupts in the guest. The thread in the // global critical region has exclusive access to the entire system and cannot // be preempted. This also means that all activity done while in the critical // region must be extremely fast (no IO!), as it has the chance to block any // other thread until its done. // // For example, in the following situation thread 1 will not be able to suspend // thread 0 until it has exited its critical region, preventing it from being // suspended while holding the table lock: // [thread 0]: // DoKernelStuff(): // auto global_lock = global_critical_region_.Acquire(); // std::lock_guard table_lock(table_mutex_); // table_->InsertStuff(); // [thread 1]: // MySuspendThread(): // auto global_lock = global_critical_region_.Acquire(); // ::SuspendThread(thread0); // // To use the region it's strongly recommended that you keep an instance near // the data requiring it. This makes it clear to those reading that the data // is protected by the global critical region. For example: // class MyType { // // Implies my_list_ is protected: // xe::global_critical_region global_critical_region_; // std::list<...> my_list_; // }; class global_critical_region { public: constexpr global_critical_region() {} static global_mutex_type& mutex(); // Acquires a lock on the global critical section. // Use this when keeping an instance is not possible. Otherwise, prefer // to keep an instance of global_critical_region near the members requiring // it to keep things readable. static global_unique_lock_type AcquireDirect() { return global_unique_lock_type(mutex()); } // Acquires a lock on the global critical section. static inline global_unique_lock_type Acquire() { return global_unique_lock_type(mutex()); } static inline void PrepareToAcquire() { swcache::PrefetchW(&mutex()); } // Acquires a deferred lock on the global critical section. static inline global_unique_lock_type AcquireDeferred() { return global_unique_lock_type(mutex(), std::defer_lock); } // Tries to acquire a lock on the glboal critical section. // Check owns_lock() to see if the lock was successfully acquired. static inline global_unique_lock_type TryAcquire() { return global_unique_lock_type(mutex(), std::try_to_lock); } }; } // namespace xe #endif // XENIA_BASE_MUTEX_H_