sceUltGetUlthreadRuntimeInfo returned success when workerThreads was null and workerCapacity was nonzero, while rejecting the same null/nonzero-capacity combination on ulthreads with ULT_ERROR_NULL. Reject null workerThreads with nonzero capacity before touching info, and add test coverage checking the error result and that info is left untouched.
sceUltGetQueueInfo reported every blocked queue thread as a waiting popper because both directions incremented one counter. Track the two wait classes in separate fields and fill both query outputs, copy one waiter record per blocked thread, and cover full-queue and empty-queue waits with a new queue-waiters test. sceUltGetUlthreadRuntimeInfo now throws when a caller asks for worker thread handles, since this runtime starts no worker threads, and the debt entry records the remaining limitations.
The console's libSceUlt exports these calls only under their
underscore names: _sceUltSemaphoreOptParamInitialize,
_sceUltQueueOptParamInitialize,
_sceUltQueueDataResourcePoolOptParamInitialize,
_sceUltWaitingQueueResourcePoolOptParamInitialize,
_sceUltUlthreadOptParamInitialize,
_sceUltReaderWriterLockOptParamInitialize and
_sceUltReaderWriterLockCreate are in the PS5 3.20 export list
(DNNDHH/PS5-3.20_Libs libSceUlt.c) and in the PS4 SDK stub list
(aerosoul94/dynlib dynlib.xml), and their names without the underscore
are in neither. Each now forwards to the export without the underscore,
passing the trailing build version, as _sceUltMutexOptParamInitialize
and _sceUltMutexCreate already do. The argument lists match the PS Vita
libult in RPCS3; that they hold on PS5 stays recorded as unmeasured in
the debt entry for the underscore forwarders.
The opt-params test also runs the six underscore initializers: a null
pointer returns ULT_ERROR_NULL, the 128-byte structure is cleared and
the bytes after it are left alone. The new rwlock-underscored test
checks that _sceUltReaderWriterLockCreate returns ULT_ERROR_NULL for a
null lock, ULT_ERROR_INVALID for an unknown pool without writing the
lock and ULT_ERROR_STATE for a second create, that it clears the
256-byte lock, and that the lock it creates on a waiting-queue pool
works with the read, write and destroy calls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceUltUlthreadRuntimeCreate clears 4096 bytes of the runtime object, and guest_ult_thread_tests already passes a 4096-byte runtime. The ulthreads case of guest_ult_sync_tests passed a 256-byte object, so the runtime creation wrote 3840 bytes past it on the stack. In a Windows Debug build the write runs off the top of the main thread stack and the test crashes with an access violation.
An ulthread runs on its own guest thread, so it remembers its object for sceUltUlthreadGetSelf, yields that thread, and exits it with the status sceUltUlthreadJoin and sceUltUlthreadTryJoin return. Outside an ulthread the three return ULT_ERROR_STATE.
Readers share the lock while no writer holds it and a writer waits until it is free. Destroying a held or waited-on lock and destroying the waiting-queue pool of a lock a thread waits on return ULT_ERROR_BUSY, and sceUltFinalize wakes waiters with ULT_ERROR_STATE, as for the mutex.
It takes a free mutex or relocks one the caller holds, as sceUltMutexLock does, and returns ULT_ERROR_BUSY instead of waiting when another thread holds it.