feat(libSceUlt): implement sceUltMutexDestroy and sceUltWaitingQueueResourcePoolDestroy

This commit is contained in:
mrfelek authored and mariusyo07 committed 2026-10-14 15:07:48 +02:00
1 parent dacd78525e
commit ca9f4e47b1
4 files changed
+152 -3

No files matched your search

+107 -2
View File
@@ -1,3 +1,4 @@
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <cstddef>
@@ -123,6 +124,7 @@ int APS5_VABI sceUltInitialize() {
int APS5_VABI sceUltFinalize() {
std::vector<std::shared_ptr<UltSemaphoreState>> semaphores;
std::vector<std::shared_ptr<UltMutexState>> mutexes;
{
std::lock_guard<std::mutex> lock(gMutex);
if (!gUlthreads.empty()) {
@@ -136,6 +138,13 @@ int APS5_VABI sceUltFinalize() {
semaphores.push_back(state);
}
gSemaphores.clear();
mutexes.reserve(gMutexes.size());
for (auto& entry : gMutexes) {
auto& state = entry.second;
std::lock_guard<std::mutex> stateLock(state->_mutex);
state->_alive = false;
mutexes.push_back(state);
}
gMutexes.clear();
gResourcePools.clear();
for (auto& entry : gQueues) {
@@ -152,6 +161,9 @@ int APS5_VABI sceUltFinalize() {
for (const auto& state : semaphores) {
state->_available.notify_all();
}
for (const auto& state : mutexes) {
state->_available.notify_all();
}
return ULT_OK;
}
@@ -172,15 +184,38 @@ int APS5_VABI sceUltMutexCreate(void* mutex, const char* name, void* waitingQueu
}
auto state = std::make_shared<UltMutexState>();
state->_attribute = optParam != nullptr ? optParam->attribute : 0;
state->_waitingPool = waitingQueueResourcePool;
std::lock_guard<std::mutex> lock(gMutex);
if (waitingQueueResourcePool != nullptr && gResourcePools.find(waitingQueueResourcePool) == gResourcePools.end()) {
return ULT_ERROR_INVALID;
}
if (gMutexes.find(mutex) != gMutexes.end()) {
return ULT_ERROR_STATE;
}
std::memset(mutex, 0, 256);
gMutexes[mutex] = std::move(state);
return ULT_OK;
}
int APS5_VABI sceUltMutexDestroy(void* mutex) {
if (mutex == nullptr) {
return ULT_ERROR_NULL;
}
std::lock_guard<std::mutex> lock(gMutex);
auto it = gMutexes.find(mutex);
if (it == gMutexes.end()) {
return ULT_ERROR_STATE;
}
auto state = it->second;
std::lock_guard<std::mutex> stateLock(state->_mutex);
if (state->_lockCount != 0 || state->_waiters != 0) {
return ULT_ERROR_BUSY;
}
state->_alive = false;
gMutexes.erase(it);
return ULT_OK;
}
int APS5_VABI sceUltMutexLock(void* mutex) {
std::shared_ptr<UltMutexState> state;
{
@@ -191,7 +226,30 @@ int APS5_VABI sceUltMutexLock(void* mutex) {
}
state = it->second;
}
state->_mutex.lock();
const auto self = std::this_thread::get_id();
std::unique_lock<std::mutex> lock(state->_mutex);
if (!state->_alive) {
return ULT_ERROR_STATE;
}
if (state->_owner == self) {
++state->_lockCount;
return ULT_OK;
}
++state->_waiters;
try {
state->_available.wait(lock, [&] {
return !state->_alive || state->_lockCount == 0;
});
} catch (...) {
--state->_waiters;
throw;
}
--state->_waiters;
if (!state->_alive) {
return ULT_ERROR_STATE;
}
state->_owner = self;
state->_lockCount = 1;
return ULT_OK;
}
@@ -205,7 +263,16 @@ int APS5_VABI sceUltMutexUnlock(void* mutex) {
}
state = it->second;
}
state->_mutex.unlock();
const auto self = std::this_thread::get_id();
std::lock_guard<std::mutex> lock(state->_mutex);
if (!state->_alive || state->_owner != self) {
return ULT_ERROR_STATE;
}
--state->_lockCount;
if (state->_lockCount == 0) {
state->_owner = {};
state->_available.notify_one();
}
return ULT_OK;
}
@@ -226,6 +293,43 @@ int APS5_VABI sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name,
return ULT_OK;
}
int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void* pool) {
if (pool == nullptr) {
return ULT_ERROR_NULL;
}
std::lock_guard<std::mutex> lock(gMutex);
auto it = gResourcePools.find(pool);
if (it == gResourcePools.end()) {
return ULT_ERROR_STATE;
}
for (const auto& entry : gMutexes) {
if (entry.second->_waitingPool == pool) {
std::lock_guard<std::mutex> stateLock(entry.second->_mutex);
if (entry.second->_waiters != 0) {
return ULT_ERROR_BUSY;
}
}
}
for (const auto& entry : gSemaphores) {
if (entry.second->_waitingPool == pool) {
std::lock_guard<std::mutex> stateLock(entry.second->_mutex);
if (entry.second->_waiters != 0) {
return ULT_ERROR_BUSY;
}
}
}
for (const auto& entry : gQueues) {
if (entry.second->_pool && entry.second->_pool->_waitingPool == pool) {
std::lock_guard<std::mutex> poolLock(entry.second->_pool->_mutex);
if (entry.second->_waiters != 0) {
return ULT_ERROR_BUSY;
}
}
}
gResourcePools.erase(it);
return ULT_OK;
}
std::uint64_t APS5_VABI sceUltQueueDataResourcePoolGetWorkAreaSize(std::uint32_t numData, std::uint64_t dataSize, std::uint32_t numQueueObject) {
if (dataSize > UINT64_MAX - 7u) throw std::out_of_range("ULT queue data size overflow");
const auto stride = alignUp(dataSize, 8u);
@@ -360,6 +464,7 @@ int APS5_VABI sceUltSemaphoreCreate(void* semaphore, const char* name, std::int3
}
auto state = std::make_shared<UltSemaphoreState>();
state->_resources = numInitialResource;
state->_waitingPool = waitingQueueResourcePool;
std::lock_guard<std::mutex> lock(gMutex);
if (waitingQueueResourcePool != nullptr && gResourcePools.find(waitingQueueResourcePool) == gResourcePools.end()) {
return ULT_ERROR_INVALID;
+9 -1
View File
@@ -6,6 +6,7 @@
#include <cstdint>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include "SceTypes.hpp"
@@ -19,8 +20,14 @@ constexpr int ULT_ERROR_BUSY = -2139029497;
constexpr int ULT_ERROR_AGAIN = -2139029496;
struct UltMutexState {
std::recursive_mutex _mutex;
std::mutex _mutex;
std::condition_variable _available;
std::thread::id _owner{};
std::uint32_t _lockCount = 0;
std::uint32_t _waiters = 0;
bool _alive = true;
std::uint32_t _attribute = 0;
void* _waitingPool = nullptr;
};
struct UltSemaphoreState {
@@ -29,6 +36,7 @@ struct UltSemaphoreState {
std::int32_t _resources = 0;
std::uint32_t _waiters = 0;
bool _alive = true;
void* _waitingPool = nullptr;
};
struct UltResourcePoolState {
+34
View File
@@ -4,6 +4,7 @@
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <chrono>
#include <thread>
extern "C" {
@@ -30,6 +31,11 @@ int APS5_VABI _sceUltQueueDataResourcePoolCreate(void*, const char*, std::uint32
int APS5_VABI _sceUltQueueCreate(void*, const char*, std::uint64_t, void*, void*, const void*, std::uint32_t);
int APS5_VABI sceUltQueuePush(void*, const void*);
int APS5_VABI sceUltQueuePop(void*, void*);
int APS5_VABI sceUltMutexDestroy(void*);
int APS5_VABI sceUltWaitingQueueResourcePoolDestroy(void*);
int APS5_VABI sceUltSemaphoreDestroy(void*);
int APS5_VABI sceUltQueueDestroy(void*);
int APS5_VABI sceUltQueueDataResourcePoolDestroy(void*);
}
static constexpr int Ok = 0;
@@ -177,6 +183,34 @@ static void UnderscoredCreate() {
std::uint64_t value = 0x1122334455667788ull, output = 0;
CHECK(sceUltQueuePush(queue, &value) == Ok);
CHECK(sceUltQueuePop(queue, &output) == Ok && output == value);
CHECK(sceUltWaitingQueueResourcePoolDestroy(nullptr) == Null);
CHECK(sceUltWaitingQueueResourcePoolDestroy(unknownPool) == State);
CHECK(sceUltMutexDestroy(nullptr) == Null);
CHECK(sceUltMutexLock(mutex) == Ok);
CHECK(sceUltMutexDestroy(mutex) == Busy);
std::atomic<bool> waiterStarted{false};
std::atomic<bool> waiterAcquired{false};
std::thread waiter([&] {
waiterStarted.store(true, std::memory_order_release);
CHECK(sceUltMutexLock(mutex) == Ok);
waiterAcquired.store(true, std::memory_order_release);
CHECK(sceUltMutexUnlock(mutex) == Ok);
});
while (!waiterStarted.load(std::memory_order_acquire)) std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(20));
CHECK(!waiterAcquired.load(std::memory_order_acquire));
CHECK(sceUltMutexDestroy(mutex) == Busy);
CHECK(sceUltWaitingQueueResourcePoolDestroy(pool) == Busy);
CHECK(sceUltMutexUnlock(mutex) == Ok);
waiter.join();
CHECK(waiterAcquired.load(std::memory_order_acquire));
CHECK(sceUltWaitingQueueResourcePoolDestroy(pool) == Ok);
CHECK(sceUltWaitingQueueResourcePoolDestroy(pool) == State);
CHECK(sceUltMutexDestroy(mutex) == Ok);
CHECK(sceUltMutexDestroy(mutex) == State);
CHECK(sceUltSemaphoreDestroy(semaphore) == Ok);
CHECK(sceUltQueueDestroy(queue) == Ok);
CHECK(sceUltQueueDataResourcePoolDestroy(dataPool) == Ok);
}
int main(int argc, char** argv) {
+2
View File
@@ -9,3 +9,5 @@
### Functional
- [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`.
- [sceUltWaitingQueueResourcePoolDestroy](../../../core/libs/prx/libSceUlt/Export.cpp) refuses with `ULT_ERROR_BUSY` only when a thread is currently waiting on a pool-backed mutex, semaphore or queue, matching the PS5 libSceUlt disassembly (0x473a in or55417wcDk); bound but idle objects do not block pool destruction. Waiting-queue pool guest work area node accounting is not implemented.
- [sceUltMutexDestroy](../../../core/libs/prx/libSceUlt/Export.cpp) returns `ULT_ERROR_BUSY` when the mutex is held or has waiting threads, tracked via owner and waiter counts in `UltMutexState`. Prosper (`hle_ult.cpp`) logs a destroy of a held mutex and returns `ULT_OK`; AnyPS5 returns `ULT_ERROR_BUSY` to prevent destroying active mutexes.