diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 3a1c831fb..2f3fb16eb 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -85,7 +85,7 @@ struct PthreadPrivate { std::atomic references{2}; void* stackAddress = nullptr; std::size_t stackSize = 0; - std::atomic* waitState = nullptr; + std::atomic waitCount{0}; std::atomic affinity{DEFAULT_THREAD_AFFINITY}; std::atomic priority{DEFAULT_THREAD_PRIORITY}; std::mutex nameLock; diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index c1aa91883..b3c368d80 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -174,7 +174,7 @@ static void RunThread(std::unique_ptr args) { const auto entry = args->entry; void* arg = args->arg; PthreadPrivate* self = args->self; - self->waitState = TimedWait::ThreadWaitState(); + TimedWait::BindThreadWaitState(&self->waitCount); if (!self->stackAddress) SetStackFromHost(self); currentThread = self; RegisterStack(self); @@ -408,7 +408,7 @@ Pthread APS5_VABI scePthreadSelf() { adopted->threadId = std::this_thread::get_id(); adopted->_detached = true; adopted->references.store(1, std::memory_order_relaxed); - adopted->waitState = TimedWait::ThreadWaitState(); + TimedWait::BindThreadWaitState(&adopted->waitCount); SetStackFromHost(adopted.get()); currentThread = adopted.release(); } diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index aef934025..958bf534f 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -6,6 +6,7 @@ #include "prx/libkernel/Pthread/include/Pthread.hpp" #include #include +#include #include #include #ifdef _WIN32 @@ -193,15 +194,13 @@ void RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) { return; } const auto native = static_cast(thread->nativeHandle); + auto queued = std::make_unique(Delivery{handler, signum, {}}); if (SuspendThread(native) == static_cast(-1)) throw std::runtime_error("sceKernelRaiseException: cannot suspend the target thread"); - if (thread->waitState != nullptr && thread->waitState->load(std::memory_order_seq_cst) > 0) { - auto* delivery = new Delivery{handler, signum, {}}; - const bool queued = QueueUserAPC(WaitingEntry, native, reinterpret_cast(delivery)) != 0; + if (thread->waitCount.load(std::memory_order_seq_cst) > 0) { + const bool accepted = QueueUserAPC(WaitingEntry, native, reinterpret_cast(queued.get())) != 0; ResumeThread(native); - if (!queued) { - delete delivery; - throw std::runtime_error("sceKernelRaiseException: cannot queue delivery to the waiting thread"); - } + if (!accepted) throw std::runtime_error("sceKernelRaiseException: cannot queue delivery to the waiting thread"); + queued.release(); return; } alignas(16) Delivery delivery{handler, signum, {}}; @@ -274,7 +273,7 @@ int APS5_VABI sceKernelRemoveExceptionHandler(int signum) { int APS5_VABI sceKernelRaiseException(Pthread thread, int signum) { if (signum != 30) return SCE_KERNEL_ERROR_EINVAL; - if (thread == nullptr) return SCE_KERNEL_ERROR_ESRCH; + if (thread == nullptr || thread->_finished.load(std::memory_order_acquire)) return SCE_KERNEL_ERROR_ESRCH; const auto handler = Handler(signum); if (handler == nullptr) throw std::runtime_error("sceKernelRaiseException: no handler installed for the signal"); #ifdef _WIN32 diff --git a/core/libs/prx/libkernel/Time/TimedWait.cpp b/core/libs/prx/libkernel/Time/TimedWait.cpp index 7fb2592c0..90fd466ad 100644 --- a/core/libs/prx/libkernel/Time/TimedWait.cpp +++ b/core/libs/prx/libkernel/Time/TimedWait.cpp @@ -17,24 +17,25 @@ namespace { constexpr std::uint64_t YIELD_BELOW_NANOS = 50000ULL; constexpr std::uint64_t LEAD_NANOS = 500000ULL; -thread_local std::atomic waitState{0}; +thread_local std::atomic ownWaitState{0}; +thread_local std::atomic* waitState = &ownWaitState; #ifdef _WIN32 DWORD AlertableWait(DWORD count, const HANDLE* handles, DWORD milliseconds) { - waitState.fetch_add(1, std::memory_order_seq_cst); + waitState->fetch_add(1, std::memory_order_seq_cst); DWORD result; do { result = WaitForMultipleObjectsEx(count, handles, FALSE, milliseconds, TRUE); } while (result == WAIT_IO_COMPLETION); - waitState.fetch_sub(1, std::memory_order_seq_cst); + waitState->fetch_sub(1, std::memory_order_seq_cst); return result; } void AlertableSleep(DWORD milliseconds) { - waitState.fetch_add(1, std::memory_order_seq_cst); + waitState->fetch_add(1, std::memory_order_seq_cst); SleepEx(milliseconds, TRUE); - waitState.fetch_sub(1, std::memory_order_seq_cst); + waitState->fetch_sub(1, std::memory_order_seq_cst); } void WINAPI DestroyWaiter(void* value); @@ -208,8 +209,8 @@ void Condition::NotifyAll() { #endif -std::atomic* ThreadWaitState() { - return &waitState; +void BindThreadWaitState(std::atomic* state) { + waitState = state != nullptr ? state : &ownWaitState; } bool Coarse() { diff --git a/core/libs/prx/libkernel/Time/include/TimedWait.hpp b/core/libs/prx/libkernel/Time/include/TimedWait.hpp index fa83866dc..bddd4189e 100644 --- a/core/libs/prx/libkernel/Time/include/TimedWait.hpp +++ b/core/libs/prx/libkernel/Time/include/TimedWait.hpp @@ -10,7 +10,7 @@ namespace TimedWait { bool Coarse(); -std::atomic* ThreadWaitState(); +void BindThreadWaitState(std::atomic* state); std::uint64_t NowNanos(); std::uint64_t DeadlineNanos(std::uint64_t microseconds); std::uint64_t RemainingMicros(std::uint64_t deadlineNanos); diff --git a/core/libs/tests/GuestRaiseException.cpp b/core/libs/tests/GuestRaiseException.cpp index c6cfd629a..3a8913473 100644 --- a/core/libs/tests/GuestRaiseException.cpp +++ b/core/libs/tests/GuestRaiseException.cpp @@ -22,6 +22,7 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time); } static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); +static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); static constexpr int SIGUSR1 = 30; static constexpr int Repeats = 100; @@ -84,6 +85,13 @@ static void* APS5_VABI HostBlocked(void* arg) { return nullptr; } +static std::atomic finishedReturned{false}; + +static void* APS5_VABI Finished(void*) { + finishedReturned.store(true); + return nullptr; +} + static void ExpectDelivery(int before, std::thread::id thread) { for (int attempt = 0; attempt < 5000 && calls.load() == before; ++attempt) std::this_thread::sleep_for(std::chrono::milliseconds(1)); Require(calls.load() == before + 1); @@ -148,5 +156,12 @@ int main() { hostLock.unlock(); Require(scePthreadJoin(blockedThread, nullptr) == 0); + Pthread finishedThread = nullptr; + Require(scePthreadCreate(&finishedThread, nullptr, Finished, nullptr, "finished") == 0); + while (!finishedReturned.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(sceKernelRaiseException(finishedThread, SIGUSR1) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadJoin(finishedThread, nullptr) == 0); + Require(sceKernelRemoveExceptionHandler(SIGUSR1) == 0); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 07ab6b6ad..ca20fa42c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -181,7 +181,12 @@ Throughout the project, every function at every stage either **does exactly what - [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported - [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out -- [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler but nothing delivers to it: a host fault still ends the process, and `sceKernelRaiseException` is not implemented. PPSA12544's PS5Util module (Unity) installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection, so delivery must run the handler on the target thread with a FreeBSD `ucontext` +- [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler, but a host fault still ends the process: only signals raised with `sceKernelRaiseException` are delivered. Unity's PS5Util module installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection +- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) delivers on Windows only and throws on Linux +- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) builds the handler's `ucontext` with FreeBSD's amd64 layout (16-byte sigmask, `mcontext` at 0x40, `mc_rsp` at 0xf8); the console's layout is not verified beyond the fields PS5Util reads +- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) does not consult the target thread's signal mask +- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) saves and restores only `CONTEXT_FULL | CONTEXT_FLOATING_POINT` on Windows, so the upper halves of the YMM registers are not preserved if the handler or the entry uses AVX +- [sceKernelRaiseException](../../core/libs/prx/libkernel/System/src/Exception.cpp) reaches a thread that waits in an alertable `TimedWait` wait through an APC. A thread blocked anywhere else, including all waits under `APS5_COARSE_TIMED_WAITS` and host waits inside the emulator, is redirected and runs the handler only when that wait returns - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. - [Thread cancellation](../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) is not modelled: `scePthreadCancel` throws, so no cancel request is ever pending and `scePthreadTestcancel` and `pthread_testcancel` return without acting; the cancel state and type set by `scePthreadSetcancelstate` and `scePthreadSetcanceltype` are stored and not used - [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws.