diff --git a/core/libs/prx/libkernel/Pthread/Posix/Once.cpp b/core/libs/prx/libkernel/Pthread/Posix/Once.cpp index 32ed2ee79..6919e008b 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Once.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Once.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" #include #include #include @@ -14,6 +15,14 @@ struct GuestOnce { static_assert(offsetof(GuestOnce, mutex) == 8 && sizeof(GuestOnce) == 16); std::mutex onceMutex; std::condition_variable onceChanged; + +void APS5_VABI ResetOnce(void* control) { + { + std::lock_guard lock(onceMutex); + static_cast(control)->state = 0; + } + onceChanged.notify_all(); +} } extern "C" int APS5_VABI pthread_once_nid_postfix( @@ -27,15 +36,17 @@ extern "C" int APS5_VABI pthread_once_nid_postfix( lock.unlock(); // Initializers may initialize other once controls. Never run guest code // under the shared lock. The lock publishes all initializer writes. + CleanupHandler reset{}; + void* const mark = ThreadCancel::CleanupMark(); + __pthread_cleanup_push_imp_nid_postfix(ResetOnce, control, &reset); try { initialize(); } catch (...) { - lock.lock(); - control->state = 0; - lock.unlock(); - onceChanged.notify_all(); + ThreadCancel::CleanupRestore(mark); + ResetOnce(control); throw; } + ThreadCancel::CleanupRestore(mark); lock.lock(); control->state = 1; lock.unlock(); diff --git a/core/libs/prx/libkernel/Pthread/include/Cancel.hpp b/core/libs/prx/libkernel/Pthread/include/Cancel.hpp index 26a7c7176..6a3dad661 100644 --- a/core/libs/prx/libkernel/Pthread/include/Cancel.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Cancel.hpp @@ -12,6 +12,13 @@ inline constexpr int GUEST_CANCEL_DEFERRED = 0; inline constexpr int GUEST_CANCEL_ASYNCHRONOUS = 2; inline void* const GUEST_CANCELED = reinterpret_cast(std::uintptr_t{1}); +struct CleanupHandler { + CleanupHandler* previous; + void (APS5_VABI* routine)(void*); + void* argument; + int onHeap; +}; + extern "C" { 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); @@ -21,6 +28,8 @@ namespace ThreadCancel { bool Requested(); void Check(); +void* CleanupMark(); +void CleanupRestore(void* mark); void BeginWait(TimedWait::Condition* condition); void EndWait(); diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 119d42352..34e9ea435 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -53,12 +53,6 @@ struct ThreadArgs { static std::atomic threadDtors{nullptr}; static std::atomic threadAtexitCount{nullptr}; static std::atomic threadAtexitReport{nullptr}; -struct CleanupHandler { - CleanupHandler* previous; - void (APS5_VABI* routine)(void*); - void* argument; - int onHeap; -}; struct GuestThreadState { PthreadPrivate* current = nullptr; @@ -371,6 +365,14 @@ void ThreadCancel::Check() { if (Requested()) scePthreadExit(GUEST_CANCELED); } +void* ThreadCancel::CleanupMark() { + return CurrentGuestState().cleanupHandlers; +} + +void ThreadCancel::CleanupRestore(void* mark) { + CurrentGuestState().cleanupHandlers = static_cast(mark); +} + void ThreadCancel::BeginWait(TimedWait::Condition* condition) { if (!CurrentGuestState().current) return; std::lock_guard lock(CurrentGuestState().current->cancelLock); @@ -819,20 +821,36 @@ int APS5_VABI scePthreadSetprio(Pthread thread, int prio) { return SCE_OK; } +static void APS5_VABI ResetOnce(void* once) { + constexpr int32_t Never = 0; + std::atomic_ref state(*static_cast(once)); + state.store(Never, std::memory_order_release); + state.notify_all(); +} + int APS5_VABI scePthreadOnce(int32_t* once, void (APS5_VABI* init)(void)) { if (!once || !init) return SCE_KERNEL_ERROR_EINVAL; constexpr int32_t Never = 0; constexpr int32_t Done = 1; constexpr int32_t Running = 2; std::atomic_ref state(*once); - int32_t expected = Never; - if (state.compare_exchange_strong(expected, Running, std::memory_order_acq_rel)) { - init(); - state.store(Done, std::memory_order_release); - state.notify_all(); - return SCE_OK; + for (int32_t expected = Never; !state.compare_exchange_strong(expected, Running, std::memory_order_acq_rel); expected = Never) { + if (expected != Running) return SCE_OK; + state.wait(Running, std::memory_order_acquire); } - while ((expected = state.load(std::memory_order_acquire)) == Running) state.wait(Running, std::memory_order_acquire); + CleanupHandler reset{}; + void* const mark = ThreadCancel::CleanupMark(); + __pthread_cleanup_push_imp_nid_postfix(ResetOnce, once, &reset); + try { + init(); + } catch (...) { + ThreadCancel::CleanupRestore(mark); + ResetOnce(once); + throw; + } + ThreadCancel::CleanupRestore(mark); + state.store(Done, std::memory_order_release); + state.notify_all(); return SCE_OK; } diff --git a/core/libs/tests/GuestOnce.cpp b/core/libs/tests/GuestOnce.cpp index b82ec22ef..3e60f58ec 100644 --- a/core/libs/tests/GuestOnce.cpp +++ b/core/libs/tests/GuestOnce.cpp @@ -1,4 +1,4 @@ -#include "prx/libc/include/general/VabiMacros.hpp" +#include "SceTypes.hpp" #include #include #include @@ -9,7 +9,17 @@ struct GuestOnce { std::int32_t state; void* mutex; }; static_assert(sizeof(GuestOnce) == 16 && offsetof(GuestOnce, mutex) == 8); using Initializer = void (APS5_VABI *)(); -extern "C" int APS5_VABI pthread_once_nid_postfix(GuestOnce*, Initializer); +extern "C" { +int APS5_VABI pthread_once_nid_postfix(GuestOnce*, Initializer); +int APS5_VABI scePthreadOnce(std::int32_t* once, Initializer init); +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); +void APS5_VABI scePthreadTestcancel(); +Pthread APS5_VABI scePthreadSelf(); +void APS5_VABI __pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info); +} static void Require(bool value) { if (!value) std::abort(); } static GuestOnce control{}, nested{}, retry{}; static std::atomic calls{0}, arrived{0}; @@ -24,6 +34,35 @@ static void APS5_VABI Initialize() { static void APS5_VABI Retry() { if (++attempts == 1) throw 7; } +static GuestOnce exited{}, cancelled{}; +static std::int32_t sceExited = 0, sceCancelled = 0; +static int retried = 0; +static void APS5_VABI ExitInsideInitializer() { scePthreadExit(nullptr); } +static void APS5_VABI CancelInsideInitializer() { + Require(scePthreadCancel(scePthreadSelf()) == 0); + scePthreadTestcancel(); +} +static void APS5_VABI CountRetry() { ++retried; } +static void APS5_VABI AbortCleanup(void*) { std::abort(); } +static void APS5_VABI PushAndThrow() { + std::uintptr_t info[8]; + __pthread_cleanup_push_imp_nid_postfix(AbortCleanup, nullptr, info); + throw 7; +} +static GuestOnce pushed{}; +static std::int32_t scePushed = 0; +struct OnceCall { GuestOnce* control; std::int32_t* sceControl; Initializer initialize; }; +static void* APS5_VABI CallOnce(void* argument) { + const auto& call = *static_cast(argument); + if (call.control) pthread_once_nid_postfix(call.control, call.initialize); + else scePthreadOnce(call.sceControl, call.initialize); + return nullptr; +} +static void LeaveInsideInitializer(OnceCall call) { + Pthread thread{}; + Require(scePthreadCreate(&thread, nullptr, CallOnce, &call, nullptr) == 0); + Require(scePthreadJoin(thread, nullptr) == 0); +} int main() { std::array workers; for (auto& worker : workers) worker = std::thread([] { @@ -41,4 +80,22 @@ int main() { Require(pthread_once_nid_postfix(&control, nullptr) == 22); GuestOnce invalid{9, nullptr}; Require(pthread_once_nid_postfix(&invalid, Initialize) == 22); + LeaveInsideInitializer({&exited, nullptr, ExitInsideInitializer}); + Require(exited.state == 0); + Require(pthread_once_nid_postfix(&exited, CountRetry) == 0 && exited.state == 1 && retried == 1); + LeaveInsideInitializer({&cancelled, nullptr, CancelInsideInitializer}); + Require(cancelled.state == 0); + Require(pthread_once_nid_postfix(&cancelled, CountRetry) == 0 && cancelled.state == 1 && retried == 2); + LeaveInsideInitializer({nullptr, &sceExited, ExitInsideInitializer}); + Require(sceExited == 0); + Require(scePthreadOnce(&sceExited, CountRetry) == 0 && sceExited == 1 && retried == 3); + LeaveInsideInitializer({nullptr, &sceCancelled, CancelInsideInitializer}); + Require(sceCancelled == 0); + Require(scePthreadOnce(&sceCancelled, CountRetry) == 0 && sceCancelled == 1 && retried == 4); + try { pthread_once_nid_postfix(&pushed, PushAndThrow); std::abort(); } + catch (int value) { Require(value == 7 && pushed.state == 0); } + Require(pthread_once_nid_postfix(&pushed, CountRetry) == 0 && pushed.state == 1 && retried == 5); + try { scePthreadOnce(&scePushed, PushAndThrow); std::abort(); } + catch (int value) { Require(value == 7 && scePushed == 0); } + Require(scePthreadOnce(&scePushed, CountRetry) == 0 && scePushed == 1 && retried == 6); }