diff --git a/CMakeLists.txt b/CMakeLists.txt index d8fe5f68b..b9ba14469 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -679,6 +679,7 @@ set(COMMON src/common/logging/backend.cpp src/common/path_util.h src/common/object_pool.h src/common/polyfill_thread.h + src/common/range_lock.h src/common/rdtsc.cpp src/common/rdtsc.h src/common/recursive_lock.cpp diff --git a/src/common/range_lock.h b/src/common/range_lock.h new file mode 100644 index 000000000..3927cdb17 --- /dev/null +++ b/src/common/range_lock.h @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Common { + +// From boost thread locking + +template +struct RangeLockGuard { + Iterator begin; + Iterator end; + + RangeLockGuard(Iterator begin_, Iterator end_) : begin(begin_), end(end_) { + LockRange(begin, end); + } + + void release() { + begin = end; + } + + ~RangeLockGuard() { + for (; begin != end; ++begin) { + begin->unlock(); + } + } +}; + +template +Iterator TryLockRange(Iterator begin, Iterator end) { + using LockType = typename std::iterator_traits::value_type; + + if (begin == end) { + return end; + } + + std::unique_lock guard(*begin, std::try_to_lock); + if (!guard.owns_lock()) { + return begin; + } + + Iterator failed = TryLockRange(++begin, end); + if (failed == end) { + guard.release(); + } + + return failed; +} + +template +void LockRange(Iterator begin, Iterator end) { + using LockType = typename std::iterator_traits::value_type; + + if (begin == end) { + return; + } + + bool start_with_begin = true; + Iterator second = begin; + ++second; + Iterator next = second; + + while (true) { + std::unique_lock begin_lock(*begin, std::defer_lock); + if (start_with_begin) { + begin_lock.lock(); + + const Iterator failed_lock = TryLockRange(next, end); + if (failed_lock == end) { + begin_lock.release(); + return; + } + + start_with_begin = false; + next = failed_lock; + } else { + RangeLockGuard guard(next, end); + + if (begin_lock.try_lock()) { + const Iterator failed_lock = TryLockRange(second, next); + if (failed_lock == next) { + begin_lock.release(); + guard.release(); + return; + } + + start_with_begin = false; + next = failed_lock; + } else { + start_with_begin = true; + next = second; + } + } + } +} + +} // namespace Common \ No newline at end of file diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp index 9a1aa1df3..15dbf909c 100644 --- a/src/video_core/page_manager.cpp +++ b/src/video_core/page_manager.cpp @@ -2,9 +2,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include #include "common/assert.h" #include "common/debug.h" +#include "common/range_lock.h" #include "common/signal_context.h" #include "core/memory.h" #include "core/signals.h" @@ -196,13 +196,9 @@ struct PageManager::Impl { const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); // Acquire locks for the range of pages - size_t lock_start = page / PAGES_PER_LOCK; - const size_t lock_end = Common::DivCeil(page_end, PAGES_PER_LOCK); - boost::container::small_vector, 8> unique_locks; - for (; lock_start < lock_end; ++lock_start) { - unique_locks.emplace_back(locks[lock_start], std::defer_lock); - } - boost::lock(unique_locks.begin(), unique_locks.end()); + const auto lock_start = locks.begin() + (page / PAGES_PER_LOCK); + const auto lock_end = locks.begin() + Common::DivCeil(page_end, PAGES_PER_LOCK); + Common::RangeLockGuard lk(lock_start, lock_end); auto perms = cached_pages[page].Perm(); u64 range_begin = 0;