From 48dcaace7868a55a831b196c46464163b816aa76 Mon Sep 17 00:00:00 2001 From: Ahir <191682168+ahir061@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:22:13 +0530 Subject: [PATCH] fix(shader): lock recompiler shared mutexes once when created winpthreads initializes a statically initialized rwlock on its first lock and fails a concurrent first lock with EINVAL, which std::shared_mutex ignores, so two threads can hold the lock at once. The source, binding plan and specialized module caches take their first locks from several threads, so their mutexes now take and release the lock in their constructor, as guest rwlocks do. The GuestWriteWatch mutex the technical debt entry also named is in the Linux-only watch, so it is not affected. --- core/shader/recompiler/Recompiler.cpp | 16 ++++++++++++---- docs/dev/TechnicalDebt.md | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 7e5883eeb..770bcef49 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -290,6 +290,14 @@ struct SourceEntry { namespace { +class PrimedSharedMutex : public std::shared_mutex { +public: + PrimedSharedMutex() { + lock(); + unlock(); + } +}; + struct ResourceProgram { explicit ResourceProgram(const RecompileRequest& request, ShaderPreparationContext* preparation = nullptr) : program(std::make_unique(PrepareResourceProgram(request, preparation))), plan(std::make_shared(ResourceMaterializer{}.ExtractPlan(*program))) {} @@ -318,7 +326,7 @@ bool FailureMemo() { } std::shared_ptr getSource(const RecompileRequest& request, ShaderPreparationContext* preparation = nullptr) { - static std::shared_mutex mutex; + static PrimedSharedMutex mutex; // Entries whose code hashes alike share a bucket; the code comparison picks the right one. static std::unordered_map, std::vector>, SourceKeyHash> sources; struct SourceKeyStorage {}; @@ -587,7 +595,7 @@ struct PreparedBindingPlan { std::once_flag ready; std::exception_ptr failure; DescriptorBindingPlan bindings; - std::shared_mutex mutex; + PrimedSharedMutex mutex; std::map, std::shared_ptr> modules; }; @@ -618,7 +626,7 @@ std::shared_ptr preparedBindingPlan(const CompiledVariant& } key.push_back(exports.size()); for (const auto mapping : exports) key.push_back(mapping); - static std::shared_mutex mutex; + static PrimedSharedMutex mutex; static std::map, std::shared_ptr> plans; std::shared_ptr plan; { @@ -647,7 +655,7 @@ std::shared_ptr specializeModule(const CompiledShaderAr key.clear(); key.push_back(artifact.variantId); for (const auto& constant : constants) key.push_back((static_cast(constant.id) << 32u) | constant.value); - static std::shared_mutex mutex; + static PrimedSharedMutex mutex; static std::map, std::shared_ptr> modules; std::shared_ptr entry; { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..cd6228162 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -7,7 +7,7 @@ - libgcc_s_seh-1.dll - libstdc++-6.dll - libwinpthread-1.dll -- winpthreads initializes a statically initialized rwlock on its first lock and fails a concurrent first lock with `EINVAL`, which `std::shared_mutex` and `std::shared_timed_mutex` ignore, so that lock does not exclude. Guest rwlocks ([Pthread.hpp](../../core/libs/prx/libkernel/Pthread/include/Pthread.hpp)) are locked once when created. The host `std::shared_mutex` objects in [Recompiler.cpp](../../core/shader/recompiler/Recompiler.cpp) and [GuestWriteWatch.cpp](../../core/libs/prx/libc/src/GuestWriteWatch.cpp) are not, and can still race on their first contended use. +- winpthreads initializes a statically initialized rwlock on its first lock and fails a concurrent first lock with `EINVAL`, which `std::shared_mutex` and `std::shared_timed_mutex` ignore, so that lock does not exclude. Guest rwlocks ([Pthread.hpp](../../core/libs/prx/libkernel/Pthread/include/Pthread.hpp)) and the host `std::shared_mutex` objects in [Recompiler.cpp](../../core/shader/recompiler/Recompiler.cpp) are locked once when created. A new host `std::shared_mutex` used on Windows needs the same. ### Silent stubs