diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index e0ac735c3..a9209d27b 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -16,6 +16,7 @@ extern "C" Pthread APS5_VABI scePthreadSelf(); #ifdef _WIN32 extern "C" void Aps5RedirectedEntryStub(); +extern "C" [[noreturn]] void Aps5RedirectedExit(CONTEXT* context, const void* vectors, void (*restore)(CONTEXT*, EXCEPTION_RECORD*)); #endif namespace { @@ -101,13 +102,21 @@ GuestExceptionHandler Handler(int signum) { #ifdef _WIN32 constexpr std::size_t RedZone = 128; constexpr std::size_t HomeArea = 32; +constexpr std::size_t VectorBytes = 16 * 32; struct Delivery { GuestExceptionHandler handler; int signum; CONTEXT context; + std::uint8_t vectors[VectorBytes]; + std::uint64_t saveVectors; }; +const bool SaveVectors = [] { + __builtin_cpu_init(); + return __builtin_cpu_supports("avx") != 0; +}(); + void Deliver(GuestExceptionHandler handler, int signum, CONTEXT& context) { GuestUcontext ucontext{}; auto& m = ucontext.mcontext; @@ -159,8 +168,7 @@ void Deliver(GuestExceptionHandler handler, int signum, CONTEXT& context) { [[noreturn]] void RedirectedEntry(Delivery* delivery) { CONTEXT context = delivery->context; Deliver(delivery->handler, delivery->signum, context); - RtlRestoreContext(&context, nullptr); - std::abort(); + Aps5RedirectedExit(&context, delivery->saveVectors != 0 ? delivery->vectors : nullptr, RtlRestoreContext); } bool StackWritable(DWORD64 low, DWORD64 high) { @@ -185,6 +193,7 @@ void CALLBACK WaitingEntry(ULONG_PTR parameter) { static_assert(HomeArea + 8 == 40, "Aps5RedirectedEntryStub finds the delivery 40 bytes above its stack pointer"); static_assert(offsetof(Delivery, context) == 16 && offsetof(CONTEXT, Rax) == 0x78 && offsetof(CONTEXT, Rbp) == 0xa0 && offsetof(CONTEXT, R15) == 0xf0, "Aps5RedirectedEntryStub stores the live registers into the delivery's context"); +static_assert(offsetof(Delivery, vectors) == 1248 && offsetof(Delivery, saveVectors) == 1760, "Aps5RedirectedEntryStub stores ymm0-ymm15 into the delivery when saveVectors is set"); bool Exited(HANDLE native) { return WaitForSingleObject(native, 0) == WAIT_OBJECT_0; @@ -198,7 +207,7 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) { return true; } const auto native = static_cast(thread->nativeHandle); - auto queued = std::make_unique(Delivery{handler, signum, {}}); + auto queued = std::make_unique(Delivery{handler, signum, {}, {}, 0}); if (SuspendThread(native) == static_cast(-1)) { if (Exited(native)) return false; throw std::runtime_error("sceKernelRaiseException: cannot suspend the target thread"); @@ -214,7 +223,7 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) { queued.release(); return true; } - alignas(16) Delivery delivery{handler, signum, {}}; + alignas(16) Delivery delivery{handler, signum, {}, {}, SaveVectors ? 1u : 0u}; delivery.context.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT; if (!GetThreadContext(native, &delivery.context)) { ResumeThread(native); @@ -262,8 +271,50 @@ asm(".text\n" " movq %r13, 280(%rsp)\n" " movq %r14, 288(%rsp)\n" " movq %r15, 296(%rsp)\n" + " cmpq $0, 1800(%rsp)\n" + " je 1f\n" + " vmovdqu %ymm0, 1288(%rsp)\n" + " vmovdqu %ymm1, 1320(%rsp)\n" + " vmovdqu %ymm2, 1352(%rsp)\n" + " vmovdqu %ymm3, 1384(%rsp)\n" + " vmovdqu %ymm4, 1416(%rsp)\n" + " vmovdqu %ymm5, 1448(%rsp)\n" + " vmovdqu %ymm6, 1480(%rsp)\n" + " vmovdqu %ymm7, 1512(%rsp)\n" + " vmovdqu %ymm8, 1544(%rsp)\n" + " vmovdqu %ymm9, 1576(%rsp)\n" + " vmovdqu %ymm10, 1608(%rsp)\n" + " vmovdqu %ymm11, 1640(%rsp)\n" + " vmovdqu %ymm12, 1672(%rsp)\n" + " vmovdqu %ymm13, 1704(%rsp)\n" + " vmovdqu %ymm14, 1736(%rsp)\n" + " vmovdqu %ymm15, 1768(%rsp)\n" + "1:\n" " leaq 40(%rsp), %rcx\n" - " jmp Aps5RedirectedEntry\n"); + " jmp Aps5RedirectedEntry\n" + ".globl Aps5RedirectedExit\n" + "Aps5RedirectedExit:\n" + " testq %rdx, %rdx\n" + " je 1f\n" + " vmovdqu 0(%rdx), %ymm0\n" + " vmovdqu 32(%rdx), %ymm1\n" + " vmovdqu 64(%rdx), %ymm2\n" + " vmovdqu 96(%rdx), %ymm3\n" + " vmovdqu 128(%rdx), %ymm4\n" + " vmovdqu 160(%rdx), %ymm5\n" + " vmovdqu 192(%rdx), %ymm6\n" + " vmovdqu 224(%rdx), %ymm7\n" + " vmovdqu 256(%rdx), %ymm8\n" + " vmovdqu 288(%rdx), %ymm9\n" + " vmovdqu 320(%rdx), %ymm10\n" + " vmovdqu 352(%rdx), %ymm11\n" + " vmovdqu 384(%rdx), %ymm12\n" + " vmovdqu 416(%rdx), %ymm13\n" + " vmovdqu 448(%rdx), %ymm14\n" + " vmovdqu 480(%rdx), %ymm15\n" + "1:\n" + " xorl %edx, %edx\n" + " jmp *%r8\n"); #endif extern "C" { diff --git a/core/libs/tests/GuestRaiseException.cpp b/core/libs/tests/GuestRaiseException.cpp index aa715601e..c353849e5 100644 --- a/core/libs/tests/GuestRaiseException.cpp +++ b/core/libs/tests/GuestRaiseException.cpp @@ -32,6 +32,7 @@ static std::atomic calls{0}; static std::atomic handlerThread; static std::atomic handlerRsp{0}; static std::atomic handlerFrame{0}; +static std::atomic clobberVectors{false}; static void APS5_VABI Handler(int signum, void* context) { Require(signum == SIGUSR1); @@ -41,6 +42,7 @@ static void APS5_VABI Handler(int signum, void* context) { handlerFrame.store(reinterpret_cast(&local)); handlerRsp.store(rsp); handlerThread.store(std::this_thread::get_id()); + if (clobberVectors.load()) asm volatile("vzeroall" ::: "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"); calls.fetch_add(1); } @@ -102,6 +104,60 @@ static void* APS5_VABI Leaving(void* arg) { return nullptr; } +static constexpr int VectorRounds = 100; +static constexpr std::size_t VectorBytes = 16 * 32; +alignas(32) static std::uint8_t vectorPattern[VectorBytes]; +alignas(32) static std::uint8_t vectorResult[VectorBytes]; +static volatile std::uint8_t vectorStop = 0; + +static void* APS5_VABI Vectors(void* arg) { + auto& worker = *static_cast(arg); + worker.id = std::this_thread::get_id(); + worker.started.store(true); + asm volatile( + "vmovdqu 0(%[pattern]), %%ymm0\n" + "vmovdqu 32(%[pattern]), %%ymm1\n" + "vmovdqu 64(%[pattern]), %%ymm2\n" + "vmovdqu 96(%[pattern]), %%ymm3\n" + "vmovdqu 128(%[pattern]), %%ymm4\n" + "vmovdqu 160(%[pattern]), %%ymm5\n" + "vmovdqu 192(%[pattern]), %%ymm6\n" + "vmovdqu 224(%[pattern]), %%ymm7\n" + "vmovdqu 256(%[pattern]), %%ymm8\n" + "vmovdqu 288(%[pattern]), %%ymm9\n" + "vmovdqu 320(%[pattern]), %%ymm10\n" + "vmovdqu 352(%[pattern]), %%ymm11\n" + "vmovdqu 384(%[pattern]), %%ymm12\n" + "vmovdqu 416(%[pattern]), %%ymm13\n" + "vmovdqu 448(%[pattern]), %%ymm14\n" + "vmovdqu 480(%[pattern]), %%ymm15\n" + "1:\n" + "pause\n" + "cmpb $0, (%[stop])\n" + "je 1b\n" + "vmovdqu %%ymm0, 0(%[result])\n" + "vmovdqu %%ymm1, 32(%[result])\n" + "vmovdqu %%ymm2, 64(%[result])\n" + "vmovdqu %%ymm3, 96(%[result])\n" + "vmovdqu %%ymm4, 128(%[result])\n" + "vmovdqu %%ymm5, 160(%[result])\n" + "vmovdqu %%ymm6, 192(%[result])\n" + "vmovdqu %%ymm7, 224(%[result])\n" + "vmovdqu %%ymm8, 256(%[result])\n" + "vmovdqu %%ymm9, 288(%[result])\n" + "vmovdqu %%ymm10, 320(%[result])\n" + "vmovdqu %%ymm11, 352(%[result])\n" + "vmovdqu %%ymm12, 384(%[result])\n" + "vmovdqu %%ymm13, 416(%[result])\n" + "vmovdqu %%ymm14, 448(%[result])\n" + "vmovdqu %%ymm15, 480(%[result])\n" + "vzeroupper\n" + : + : [pattern] "r"(vectorPattern), [result] "r"(vectorResult), [stop] "r"(&vectorStop) + : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"); + return nullptr; +} + static std::atomic finishedReturned{false}; static void* APS5_VABI Finished(void*) { @@ -191,6 +247,24 @@ int main() { Require(scePthreadJoin(leavingThread, nullptr) == 0); Require(sceKernelDeleteSema(leaving.sem) == 0); + if (__builtin_cpu_supports("avx")) { + for (std::size_t i = 0; i < VectorBytes; ++i) vectorPattern[i] = static_cast(i * 7 + 1); + clobberVectors.store(true); + Worker vectors; + Pthread vectorsThread = nullptr; + Require(scePthreadCreate(&vectorsThread, nullptr, Vectors, &vectors, "vectors") == 0); + while (!vectors.started.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + for (int raised = 0; raised < VectorRounds; ++raised) { + Require(sceKernelRaiseException(vectorsThread, SIGUSR1) == 0); + ExpectDelivery(1 + 2 * Repeats + HostRounds + LeavingRounds + raised, vectors.id); + } + clobberVectors.store(false); + vectorStop = 1; + Require(scePthreadJoin(vectorsThread, nullptr) == 0); + Require(std::memcmp(vectorPattern, vectorResult, VectorBytes) == 0); + } + Pthread finishedThread = nullptr; Require(scePthreadCreate(&finishedThread, nullptr, Finished, nullptr, "finished") == 0); while (!finishedReturned.load()) std::this_thread::yield(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 6e8898abd..743acb84a 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -237,7 +237,6 @@ Throughout the project, every function at every stage either **does exactly what - [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