#include "SceTypes.hpp" #include #include #include #include #include extern "C" { int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); int APS5_VABI scePthreadJoin(Pthread thread, void** retval); void APS5_VABI scePthreadExit(void* retval); int APS5_VABI scePthreadCancel(Pthread thread); int APS5_VABI scePthreadSetcancelstate(int state, int* old_state); int APS5_VABI scePthreadSetcanceltype(int type, int* old_type); void APS5_VABI scePthreadTestcancel(); int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); int APS5_VABI scePthreadSemInit(PthreadSem* sem, int flag, unsigned int value, const char* name); int APS5_VABI scePthreadSemDestroy(PthreadSem* sem); int APS5_VABI scePthreadSemWait(PthreadSem* sem); int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds); int APS5_VABI pthread_cancel_nid_postfix(Pthread thread); int APS5_VABI pthread_setcanceltype_nid_postfix(int type, int* old_type); int APS5_VABI sem_init_nid_postfix(void* sem, int pshared, unsigned int value); int APS5_VABI sem_destroy_nid_postfix(void* sem); int APS5_VABI sem_wait_nid_postfix(void* sem); void APS5_VABI __pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info); void APS5_VABI __pthread_cleanup_pop_imp_nid_postfix(int execute); } static constexpr int SCE_OK = 0; static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast(0x80020003); static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); static constexpr int GUEST_ESRCH = 3; static constexpr int GUEST_EINVAL = 22; static constexpr int CANCEL_ENABLE = 0; static constexpr int CANCEL_DISABLE = 1; static constexpr int CANCEL_DEFERRED = 0; static constexpr int CANCEL_ASYNCHRONOUS = 2; static void* const CANCELED = reinterpret_cast(std::uintptr_t{1}); static void Require(bool value) { if (!value) std::abort(); } struct Shared { PthreadMutex mutex = nullptr; PthreadCond cond = nullptr; PthreadSem sem = nullptr; void* posixSem = nullptr; Pthread target = nullptr; std::atomic ready{false}; std::atomic release{false}; std::atomic returned{false}; std::atomic survived{false}; std::atomic cleanups{0}; int order[2] = {0, 0}; }; static Shared shared; static void APS5_VABI UnlockMutex(void*) { Require(scePthreadMutexUnlock(&shared.mutex) == SCE_OK); shared.cleanups.fetch_add(1); } static void APS5_VABI RecordOrder(void* arg) { shared.order[shared.cleanups.fetch_add(1)] = static_cast(reinterpret_cast(arg)); } static void* APS5_VABI CondWaiter(void*) { std::uintptr_t info[8]; Require(scePthreadMutexLock(&shared.mutex) == SCE_OK); __pthread_cleanup_push_imp_nid_postfix(UnlockMutex, nullptr, info); shared.ready.store(true); for (;;) Require(scePthreadCondWait(&shared.cond, &shared.mutex) == SCE_OK); } static void* APS5_VABI SemWaiter(void*) { shared.ready.store(true); scePthreadSemWait(&shared.sem); shared.returned.store(true); return nullptr; } static void* APS5_VABI PosixSemWaiter(void*) { shared.ready.store(true); sem_wait_nid_postfix(&shared.posixSem); shared.returned.store(true); return nullptr; } static void* APS5_VABI Idler(void*) { while (!shared.release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); return nullptr; } static void* APS5_VABI Joiner(void*) { shared.ready.store(true); scePthreadJoin(shared.target, nullptr); shared.returned.store(true); return nullptr; } static void* APS5_VABI Sleeper(void*) { shared.ready.store(true); for (;;) sceKernelUsleep_nid_postfix(1000); } static void* APS5_VABI Disabled(void*) { int old = -1; Require(scePthreadSetcancelstate(CANCEL_DISABLE, &old) == SCE_OK && old == CANCEL_ENABLE); shared.ready.store(true); while (!shared.release.load()) { sceKernelUsleep_nid_postfix(1000); scePthreadTestcancel(); } shared.survived.store(true); Require(scePthreadSetcancelstate(CANCEL_ENABLE, &old) == SCE_OK && old == CANCEL_DISABLE); scePthreadTestcancel(); shared.returned.store(true); return nullptr; } static void* APS5_VABI Asynchronous(void*) { int old = -1; Require(scePthreadSetcancelstate(CANCEL_DISABLE, nullptr) == SCE_OK); Require(scePthreadSetcanceltype(CANCEL_ASYNCHRONOUS, &old) == SCE_OK && old == CANCEL_DEFERRED); shared.ready.store(true); while (!shared.release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); scePthreadSetcancelstate(CANCEL_ENABLE, nullptr); shared.returned.store(true); return nullptr; } static void* APS5_VABI Handlers(void*) { std::uintptr_t first[8]; std::uintptr_t second[8]; __pthread_cleanup_push_imp_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{1}), first); __pthread_cleanup_push_imp_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{2}), second); __pthread_cleanup_pop_imp_nid_postfix(0); __pthread_cleanup_push_imp_nid_postfix(RecordOrder, reinterpret_cast(std::intptr_t{3}), second); __pthread_cleanup_pop_imp_nid_postfix(1); scePthreadExit(reinterpret_cast(std::intptr_t{7})); return nullptr; } static Pthread Start(PthreadEntry entry) { shared.ready.store(false); shared.release.store(false); shared.returned.store(false); shared.cleanups.store(0); Pthread thread = nullptr; Require(scePthreadCreate(&thread, nullptr, entry, nullptr, nullptr) == SCE_OK); while (!shared.ready.load()) std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(20)); return thread; } static void JoinCanceled(Pthread thread) { void* result = nullptr; Require(scePthreadJoin(thread, &result) == SCE_OK); Require(result == CANCELED); Require(!shared.returned.load()); } int main() { Require(scePthreadCancel(nullptr) == SCE_KERNEL_ERROR_ESRCH); Require(pthread_cancel_nid_postfix(nullptr) == GUEST_ESRCH); Require(scePthreadSetcancelstate(7, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadSetcanceltype(1, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_setcanceltype_nid_postfix(1, nullptr) == GUEST_EINVAL); Require(scePthreadMutexInit(&shared.mutex, nullptr, nullptr) == SCE_OK); Require(scePthreadCondInit(&shared.cond, nullptr, nullptr) == SCE_OK); Require(scePthreadSemInit(&shared.sem, 0, 0, nullptr) == SCE_OK); Require(sem_init_nid_postfix(&shared.posixSem, 0, 0) == 0); Pthread thread = Start(CondWaiter); Require(scePthreadMutexLock(&shared.mutex) == SCE_OK); Require(scePthreadCancel(thread) == SCE_OK); Require(shared.cleanups.load() == 0); Require(scePthreadMutexUnlock(&shared.mutex) == SCE_OK); JoinCanceled(thread); Require(shared.cleanups.load() == 1); Require(scePthreadMutexLock(&shared.mutex) == SCE_OK); Require(scePthreadMutexUnlock(&shared.mutex) == SCE_OK); thread = Start(SemWaiter); Require(scePthreadCancel(thread) == SCE_OK); JoinCanceled(thread); thread = Start(PosixSemWaiter); Require(pthread_cancel_nid_postfix(thread) == 0); JoinCanceled(thread); Require(scePthreadCreate(&shared.target, nullptr, Idler, nullptr, nullptr) == SCE_OK); thread = Start(Joiner); Require(scePthreadCancel(thread) == SCE_OK); JoinCanceled(thread); shared.release.store(true); Require(scePthreadJoin(shared.target, nullptr) == SCE_OK); thread = Start(Sleeper); Require(scePthreadCancel(thread) == SCE_OK); JoinCanceled(thread); shared.survived.store(false); thread = Start(Disabled); Require(scePthreadCancel(thread) == SCE_OK); std::this_thread::sleep_for(std::chrono::milliseconds(20)); shared.release.store(true); JoinCanceled(thread); Require(shared.survived.load()); thread = Start(Asynchronous); Require(scePthreadCancel(thread) == SCE_OK); shared.release.store(true); JoinCanceled(thread); shared.ready.store(true); shared.cleanups.store(0); void* result = nullptr; Require(scePthreadCreate(&thread, nullptr, Handlers, nullptr, nullptr) == SCE_OK); Require(scePthreadJoin(thread, &result) == SCE_OK); Require(reinterpret_cast(result) == 7); Require(shared.cleanups.load() == 2 && shared.order[0] == 3 && shared.order[1] == 1); Require(sem_destroy_nid_postfix(&shared.posixSem) == 0); Require(scePthreadSemDestroy(&shared.sem) == SCE_OK); Require(scePthreadCondDestroy(&shared.cond) == SCE_OK); Require(scePthreadMutexDestroy(&shared.mutex) == SCE_OK); }