From 13cc97f0dddd8852db94ca26167cb192fe807e89 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 05:52:32 +0000 Subject: [PATCH 01/31] refactor(libc): reach the guest FPU state through GuestCpu The fenv functions read and write the x87 control, status and environment and MXCSR through GuestCpu's inline helpers, which issue the same x87 and SSE instructions, so a build for another host replaces them in one header. feholdexcept keeps the exceptions masked as the fnstenv it replaces did. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 69 ++++++++++++++++++++++++ core/libs/prx/libc/src/FloatingPoint.cpp | 61 ++++++++------------- core/libs/prx/libc/src/Math.cpp | 60 +++++++++------------ 3 files changed, 116 insertions(+), 74 deletions(-) create mode 100644 core/libs/prx/libc/include/GuestCpu.hpp diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp new file mode 100644 index 000000000000..953317094e93 --- /dev/null +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -0,0 +1,69 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTCPU_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTCPU_HPP + +#include +#include + +#if defined(__x86_64__) + +#include + +namespace GuestCpu { + +inline std::uint32_t Mxcsr() { return _mm_getcsr(); } +inline void SetMxcsr(const std::uint32_t value) { _mm_setcsr(value); } + +inline std::uint16_t X87Control() { + std::uint16_t control; + asm volatile("fnstcw %0" : "=m"(control)); + return control; +} + +inline void SetX87Control(const std::uint16_t value) { + asm volatile("fldcw %0" : : "m"(value)); +} + +inline std::uint16_t X87Status() { + std::uint16_t status; + asm volatile("fnstsw %0" : "=am"(status)); + return status; +} + +inline void SetX87Status(const std::uint16_t value) { + std::array environment; + asm volatile("fnstenv %0" : "=m"(environment)); + environment[1] = (environment[1] & 0xffff0000u) | value; + asm volatile("fldenv %0" : : "m"(environment)); +} + +inline void X87ClearExceptions() { asm volatile("fnclex"); } +inline void X87Wait() { asm volatile("fwait"); } + +inline std::uint32_t MxcsrMask() { + alignas(16) std::array state; + asm volatile("fxsave %0" : "=m"(state)); + return state[7] != 0 ? state[7] : 0xffbfu; +} + +inline std::array X87Environment() { + std::array environment; + asm volatile("fnstenv %0\n\tfldcw %0" : "+m"(environment)); + return environment; +} + +inline void SetX87Environment(const std::array& environment) { + asm volatile("fldenv %0" : : "m"(environment) : "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"); +} + +inline void RaiseInvalidForSignalingNan(const float value) { + volatile int unordered = _mm_ucomieq_ss(_mm_set_ss(value), _mm_setzero_ps()); + (void)unordered; +} + +} + +#else +#error "GuestCpu.hpp supports only x86-64 targets" +#endif + +#endif diff --git a/core/libs/prx/libc/src/FloatingPoint.cpp b/core/libs/prx/libc/src/FloatingPoint.cpp index 9ba53501596d..3d3d586af16e 100644 --- a/core/libs/prx/libc/src/FloatingPoint.cpp +++ b/core/libs/prx/libc/src/FloatingPoint.cpp @@ -3,7 +3,8 @@ #include #include #include -#include + +#include "prx/libc/include/GuestCpu.hpp" namespace { @@ -11,21 +12,16 @@ constexpr unsigned Exceptions = 0x3f; constexpr unsigned Rounding = 0xc00; void ReplaceFlags(unsigned mask, unsigned flags) { - std::array environment; - asm volatile("fnstenv %0" : "=m"(environment)); - environment[1] = (environment[1] & ~mask) | (flags & mask); - asm volatile("fldenv %0" : : "m"(environment)); - _mm_setcsr((_mm_getcsr() & ~mask) | (flags & mask)); + GuestCpu::SetX87Status(static_cast((GuestCpu::X87Status() & ~mask) | (flags & mask))); + GuestCpu::SetMxcsr((GuestCpu::Mxcsr() & ~mask) | (flags & mask)); } int ChangeTraps(int exceptions, bool enable) { const auto mask = static_cast(exceptions) & Exceptions; - std::uint16_t control; - asm volatile("fnstcw %0" : "=m"(control)); - const unsigned csr = _mm_getcsr(); - const auto changed = static_cast(enable ? (control & ~mask) : (control | mask)); - asm volatile("fldcw %0" : : "m"(changed)); - _mm_setcsr(enable ? (csr & ~(mask << 7)) : (csr | (mask << 7))); + const std::uint16_t control = GuestCpu::X87Control(); + const unsigned csr = GuestCpu::Mxcsr(); + GuestCpu::SetX87Control(static_cast(enable ? (control & ~mask) : (control | mask))); + GuestCpu::SetMxcsr(enable ? (csr & ~(mask << 7)) : (csr | (mask << 7))); return static_cast(~((csr >> 7) | control) | ~Exceptions); } @@ -34,26 +30,19 @@ int ChangeTraps(int exceptions, bool enable) { extern "C" { int APS5_VABI fegetround_nid_postfix() { - std::uint16_t control; - asm volatile("fnstcw %0" : "=m"(control)); - return control & Rounding; + return GuestCpu::X87Control() & Rounding; } int APS5_VABI fesetround_nid_postfix(int rounding) { const auto mode = static_cast(rounding); if ((mode & ~Rounding) != 0) return -1; - std::uint16_t control; - asm volatile("fnstcw %0" : "=m"(control)); - control = static_cast((control & ~Rounding) | mode); - asm volatile("fldcw %0" : : "m"(control)); - _mm_setcsr((_mm_getcsr() & ~(Rounding << 3)) | (mode << 3)); + GuestCpu::SetX87Control(static_cast((GuestCpu::X87Control() & ~Rounding) | mode)); + GuestCpu::SetMxcsr((GuestCpu::Mxcsr() & ~(Rounding << 3)) | (mode << 3)); return 0; } int APS5_VABI fetestexcept_nid_postfix(int exceptions) { - std::uint16_t status; - asm volatile("fnstsw %0" : "=am"(status)); - return static_cast((status | _mm_getcsr()) & static_cast(exceptions)); + return static_cast((GuestCpu::X87Status() | GuestCpu::Mxcsr()) & static_cast(exceptions)); } int APS5_VABI fegetexceptflag_nid_postfix(std::uint16_t* flags, int exceptions) { @@ -68,12 +57,9 @@ void APS5_VABI _Feraise_nid_postfix(int exceptions) { auto raised = static_cast(exceptions); if ((raised & Overflows) != 0) raised |= Inexact; *__error_nid_postfix() = GuestEdom; - std::array environment; - asm volatile("fnstenv %0" : "=m"(environment)); - environment[1] = (environment[1] & ~raised) | (raised & 0xffff); - asm volatile("fldenv %0" : : "m"(environment)); - _mm_setcsr((_mm_getcsr() & ~raised) | (raised & 0xffff)); - asm volatile("fwait"); + GuestCpu::SetX87Status(static_cast((GuestCpu::X87Status() & ~raised) | (raised & 0xffff))); + GuestCpu::SetMxcsr((GuestCpu::Mxcsr() & ~raised) | (raised & 0xffff)); + GuestCpu::X87Wait(); if ((raised & Invalid) != 0) *__error_nid_postfix() = GuestEdom; else if ((raised & Ranges) != 0) *__error_nid_postfix() = GuestErange; } @@ -88,15 +74,15 @@ int APS5_VABI feraiseexcept_nid_postfix(int exceptions) { const auto raised = static_cast(exceptions); if ((raised & ~Exceptions) != 0) throw std::invalid_argument("feraiseexcept: unsupported exception bits"); ReplaceFlags(raised, raised); - asm volatile("fwait"); + GuestCpu::X87Wait(); return 0; } int APS5_VABI feclearexcept_nid_postfix(int exceptions) { const auto mask = static_cast(exceptions) & Exceptions; if (mask == Exceptions) { - asm volatile("fnclex"); - _mm_setcsr(_mm_getcsr() & ~Exceptions); + GuestCpu::X87ClearExceptions(); + GuestCpu::SetMxcsr(GuestCpu::Mxcsr() & ~Exceptions); } else if (mask != 0) { ReplaceFlags(mask, 0); } @@ -120,9 +106,7 @@ int APS5_VABI __fedisableexcept_nid_postfix(int exceptions) { } int APS5_VABI fegettrapenable_nid_postfix() { - std::uint16_t control; - asm volatile("fnstcw %0" : "=m"(control)); - return ~control & Exceptions; + return ~GuestCpu::X87Control() & Exceptions; } int APS5_VABI fegetexcept_nid_postfix() { @@ -132,11 +116,8 @@ int APS5_VABI fegetexcept_nid_postfix() { int APS5_VABI fesettrapenable_nid_postfix(int exceptions) { const auto mask = static_cast(exceptions); if (mask > Exceptions) return 1; - std::uint16_t control; - asm volatile("fnstcw %0" : "=m"(control)); - control = static_cast(control & ~mask); - asm volatile("fldcw %0" : : "m"(control)); - _mm_setcsr(_mm_getcsr() & ~(static_cast(mask) << 7)); + GuestCpu::SetX87Control(static_cast(GuestCpu::X87Control() & ~mask)); + GuestCpu::SetMxcsr(GuestCpu::Mxcsr() & ~(static_cast(mask) << 7)); return 0; } diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 80eeade68939..8bad9a1a3ff3 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -175,9 +175,9 @@ #include #include #include -#include #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestCpu.hpp" #include "prx/libc/include/SonyCrt.hpp" namespace { @@ -3629,8 +3629,7 @@ float APS5_VABI modff_nid_postfix(float x, float* integral) { if (exponent >= 150u) { *integral = x; if (exponent == 0xffu && (bits & 0x7fffffu) != 0) { - volatile int unordered = _mm_ucomieq_ss(_mm_set_ss(x), _mm_setzero_ps()); - (void)unordered; + GuestCpu::RaiseInvalidForSignalingNan(x); return x; } return zero; @@ -3935,8 +3934,7 @@ long double APS5_VABI _LSinh_nid_postfix(long double x, long double y) { } int APS5_VABI _Fltrounds_nid_postfix(void) { - std::uint16_t control; - asm volatile("fnstcw %0" : "=m"(control)); + const std::uint16_t control = GuestCpu::X87Control(); constexpr std::array Rounding{1, 3, 2, 0}; return Rounding[(control >> 10u) & 3u]; } @@ -3946,40 +3944,35 @@ static_assert(sizeof(GuestFloatingPointEnvironment) == 32 && alignof(GuestFloati int APS5_VABI feupdateenv_nid_postfix(const GuestFloatingPointEnvironment* environment) { if (environment == nullptr) throw std::invalid_argument("feupdateenv: null environment"); const auto saved = *environment; - alignas(16) std::array state; - asm volatile("fxsave %0" : "=m"(state)); - const auto supported = state[7] != 0 ? state[7] : 0xffbfu; - if ((saved.mxcsr & ~supported) != 0) throw std::invalid_argument("feupdateenv: unsupported MXCSR bits"); - std::uint16_t status; - asm volatile("fnstsw %0" : "=am"(status)); - const auto pending = (status | _mm_getcsr()) & 0x3f; - asm volatile("fldenv %0" : : "m"(saved) : "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"); - _mm_setcsr(saved.mxcsr); - GuestFloatingPointEnvironment raised; - asm volatile("fnstenv %0" : "=m"(raised)); - raised.status |= pending; - const auto mxcsr = _mm_getcsr() | pending; - asm volatile("fldenv %0" : : "m"(raised) : "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"); - _mm_setcsr(mxcsr); - asm volatile("fwait"); + if ((saved.mxcsr & ~GuestCpu::MxcsrMask()) != 0) throw std::invalid_argument("feupdateenv: unsupported MXCSR bits"); + const auto pending = (GuestCpu::X87Status() | GuestCpu::Mxcsr()) & 0x3f; + std::array x87; + std::memcpy(x87.data(), &saved, sizeof(x87)); + GuestCpu::SetX87Environment(x87); + GuestCpu::SetMxcsr(saved.mxcsr); + GuestCpu::SetX87Status(static_cast(GuestCpu::X87Status() | pending)); + GuestCpu::SetMxcsr(GuestCpu::Mxcsr() | pending); + GuestCpu::X87Wait(); return 0; } int APS5_VABI fegetenv_nid_postfix(GuestFloatingPointEnvironment* environment) { if (environment == nullptr) throw std::invalid_argument("fegetenv: null environment"); - asm volatile("fnstenv %0\n\tfldcw %0" : "+m"(*environment)); - environment->mxcsr = _mm_getcsr(); + const auto x87 = GuestCpu::X87Environment(); + std::memcpy(environment, x87.data(), sizeof(x87)); + environment->mxcsr = GuestCpu::Mxcsr(); return 0; } int APS5_VABI feholdexcept_nid_postfix(GuestFloatingPointEnvironment* environment) { if (environment == nullptr) throw std::invalid_argument("feholdexcept: null environment"); - const auto csr = _mm_getcsr(); - asm volatile("fnstenv %0\n\tfnclex" : "=m"(*environment)); - const auto masked = static_cast(environment->control | 0x3fu); - asm volatile("fldcw %0" : : "m"(masked)); + const auto csr = GuestCpu::Mxcsr(); + const auto x87 = GuestCpu::X87Environment(); + std::memcpy(environment, x87.data(), sizeof(x87)); + GuestCpu::X87ClearExceptions(); + GuestCpu::SetX87Control(static_cast(x87[0] | 0x3fu)); environment->mxcsr = csr; - _mm_setcsr((csr & ~0x3fu) | 0x1f80u); + GuestCpu::SetMxcsr((csr & ~0x3fu) | 0x1f80u); return 0; } @@ -4007,12 +4000,11 @@ SonyDconst _LSnan_nid_postfix {{1, 0, 0, 0x8000, 0x7fff, 0, 0, 0}}; int APS5_VABI fesetenv_nid_postfix(const GuestFloatingPointEnvironment* environment) { if (environment == nullptr) throw std::invalid_argument("fesetenv: null environment"); - alignas(16) std::array state; - asm volatile("fxsave %0" : "=m"(state)); - const auto supported = state[7] != 0 ? state[7] : 0xffbfu; - if ((environment->mxcsr & ~supported) != 0) throw std::invalid_argument("fesetenv: unsupported MXCSR bits"); - asm volatile("fldenv %0" : : "m"(*environment) : "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"); - _mm_setcsr(environment->mxcsr); + if ((environment->mxcsr & ~GuestCpu::MxcsrMask()) != 0) throw std::invalid_argument("fesetenv: unsupported MXCSR bits"); + std::array x87; + std::memcpy(x87.data(), environment, sizeof(x87)); + GuestCpu::SetX87Environment(x87); + GuestCpu::SetMxcsr(environment->mxcsr); return 0; } } From d600baaa91af961d36a2f9f338e5b493b57ef040 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Sun, 1 Nov 2026 17:45:18 +0000 Subject: [PATCH 02/31] refactor(libkernel): read the timestamp counter through GuestCpu sceKernelReadTsc and the TSC frequency measurement read the counter through GuestCpu::ReadTsc, which is __rdtsc. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 3 +++ core/libs/prx/libkernel/Time/Time.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp index 953317094e93..20695cb270a4 100644 --- a/core/libs/prx/libc/include/GuestCpu.hpp +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -6,10 +6,13 @@ #if defined(__x86_64__) +#include #include namespace GuestCpu { +inline std::uint64_t ReadTsc() { return __rdtsc(); } + inline std::uint32_t Mxcsr() { return _mm_getcsr(); } inline void SetMxcsr(const std::uint32_t value) { _mm_setcsr(value); } diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index 8bed82f352a2..e759bfb8c0a7 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include "prx/libc/include/GuestCpu.hpp" #ifdef _WIN32 #include @@ -876,17 +876,17 @@ int APS5_VABI sceKernelGettimezone(KernelTimezone* tz) { } uint64_t APS5_VABI sceKernelReadTsc(void) { - return __rdtsc(); + return GuestCpu::ReadTsc(); } std::uint64_t APS5_VABI HostTscFrequency_nid_no_patch() { static const std::uint64_t frequency = [] { const CurrentCpuPin pin; const std::uint64_t startNanos = RawMonotonicNanos(); - const std::uint64_t startTicks = __rdtsc(); + const std::uint64_t startTicks = GuestCpu::ReadTsc(); TimedWait::SleepNanos(20000000ULL); const std::uint64_t elapsedNanos = RawMonotonicNanos() - startNanos; - const std::uint64_t elapsedTicks = __rdtsc() - startTicks; + const std::uint64_t elapsedTicks = GuestCpu::ReadTsc() - startTicks; return static_cast(static_cast(elapsedTicks) * 1000000000.0L / static_cast(elapsedNanos)); }(); return frequency; From 5842bfb0cc785df33c9ba5e8becb6b56192bf776 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 13:39:31 +0000 Subject: [PATCH 03/31] refactor(agc): spin and diff guest memory portably Spin loops in the driver yield through PreciseSpinPause, which is now defined on every platform and uses the aarch64 yield hint, and WriteChanged falls back to its byte loop on targets other than x86-64. Co-Authored-By: Claude Opus 5.5 --- .../Execution/include/HandoffSpin.hpp | 8 ++---- .../src/Driver/Synchronization/WaitMemory.cpp | 4 +-- .../Execution/src/GuestMemory.cpp | 14 ++++++++-- .../Graphics/include/SlimMutex.hpp | 8 ++---- core/libs/prx/libc/include/PreciseWait.hpp | 26 ++++++++++--------- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp index 6c4b8213eaaf..2ef14eceec4e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/HandoffSpin.hpp @@ -8,16 +8,12 @@ #include #include #include -#if defined(__x86_64__) || defined(_M_X64) -#include -#endif +#include "prx/libc/include/PreciseWait.hpp" namespace AgcDriver { inline void SpinPause() { -#if defined(__x86_64__) || defined(_M_X64) - _mm_pause(); -#endif + PreciseSpinPause(); } inline unsigned UsableProcessors() { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp index 0349559d6763..aa65611a8fc2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/WaitMemory.cpp @@ -13,7 +13,7 @@ #ifdef _WIN32 #include #endif -#include +#include "prx/libc/include/PreciseWait.hpp" #include #include #include @@ -366,7 +366,7 @@ void Driver::waitMemory(std::span packet, std::uint32_t que } ++polls; if (spinning) { - _mm_pause(); + PreciseSpinPause(); if ((polls & 63u) != 0) continue; if (std::chrono::steady_clock::now() - spinStart > spinLimit) spinning = false; } else if (pauseSpin || polls >= 4000) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 73071a0ce58c..d408bc0e5ccb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -16,12 +16,14 @@ #include #include #include +#if defined(__x86_64__) || defined(_M_X64) #include #if defined(_MSC_VER) && !defined(__clang__) #include #else #include #endif +#endif #include #include #include @@ -3057,12 +3059,15 @@ void WriteChangedCommitted(std::uint64_t address, std::span cur } } +#if defined(__x86_64__) || defined(_M_X64) static std::uint32_t changedByteCount(__m128i same) { const auto sums = _mm_sad_epu8(_mm_and_si128(same, _mm_set1_epi8(1)), _mm_setzero_si128()); return 16u - static_cast(_mm_cvtsi128_si32(_mm_add_epi32(sums, _mm_unpackhi_epi64(sums, sums)))); } +#endif std::uint32_t ChangedByteStoreLimit() { +#if defined(__x86_64__) || defined(_M_X64) static const auto limit = [] { #if defined(_MSC_VER) && !defined(__clang__) int registers[4]{}; @@ -3075,13 +3080,16 @@ std::uint32_t ChangedByteStoreLimit() { return ebx == 0x68747541u && edx == 0x69746e65u && ecx == 0x444d4163u ? 16u : 4u; }(); return limit; +#else + return 16u; +#endif } void WriteChanged(std::uint64_t address, std::span current, std::span original) { WriteChanged(address, current, original, ChangedByteStoreLimit()); } -static void writeChanged(std::uint64_t address, std::span current, std::span original, std::uint32_t byteStoreLimit, bool keepGuest) { +static void writeChanged(std::uint64_t address, std::span current, std::span original, [[maybe_unused]] std::uint32_t byteStoreLimit, bool keepGuest) { require(current.size() == original.size(), "write-back snapshot sizes differ"); BreakProofs(); if (current.empty()) return; @@ -3097,8 +3105,9 @@ static void writeChanged(std::uint64_t address, std::span curre CheckRange(destination, size, 1, true); std::size_t firstChanged = size; std::size_t lastChanged = 0; - bool masked = false; std::size_t at = 0; +#if defined(__x86_64__) || defined(_M_X64) + bool masked = false; for (; at + 16 <= size; at += 16) { const auto bytes = _mm_loadu_si128(reinterpret_cast(current.data() + at)); const auto uploaded = _mm_loadu_si128(reinterpret_cast(original.data() + at)); @@ -3121,6 +3130,7 @@ static void writeChanged(std::uint64_t address, std::span curre lastChanged = std::max(lastChanged, at + 32 - static_cast(std::countl_zero(changed))); } if (masked) _mm_sfence(); +#endif for (; at < size; ++at) { if (current[at] == original[at] || (keepGuest && destination[at] != original[at])) continue; destination[at] = current[at]; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/SlimMutex.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/SlimMutex.hpp index 32de92284c23..ae5a9c29bb82 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/SlimMutex.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/SlimMutex.hpp @@ -3,9 +3,7 @@ #include #include -#if defined(__x86_64__) || defined(_M_X64) -#include -#endif +#include "prx/libc/include/PreciseWait.hpp" namespace AgcDriver::Graphics { @@ -21,9 +19,7 @@ class SlimMutex { while (locked.load(std::memory_order_relaxed)) { if (spins < 128) { ++spins; -#if defined(__x86_64__) || defined(_M_X64) - _mm_pause(); -#endif + PreciseSpinPause(); } else { std::this_thread::yield(); } diff --git a/core/libs/prx/libc/include/PreciseWait.hpp b/core/libs/prx/libc/include/PreciseWait.hpp index 1f5983845832..acacf9eb0583 100644 --- a/core/libs/prx/libc/include/PreciseWait.hpp +++ b/core/libs/prx/libc/include/PreciseWait.hpp @@ -20,6 +20,20 @@ inline constexpr std::uint64_t PrecisePauseThresholdTicks(std::uint64_t freq) { return PreciseHundredNanosToQpcTicks(2ULL, freq); } +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) +#include +#endif + +inline void PreciseSpinPause() { +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) + _mm_pause(); +#elif defined(__aarch64__) + asm volatile("yield" ::: "memory"); +#else + std::atomic_signal_fence(std::memory_order_seq_cst); +#endif +} + #ifdef _WIN32 #include #include "prx/libc/include/HostThreadLocal.hpp" @@ -119,18 +133,6 @@ inline void PreciseSleepHundredNanos(unsigned long long hundredNanos) { #ifdef __linux__ #include #endif -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) -#include -#endif - -inline void PreciseSpinPause() { -#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) - _mm_pause(); -#else - std::atomic_signal_fence(std::memory_order_seq_cst); -#endif -} - inline std::uint64_t PreciseMonotonicNanos() { struct timespec ts; #if defined(__APPLE__) && defined(_LIBCPP_VERSION) && defined(CLOCK_MONOTONIC_RAW) From dcc553133f60bcda593312ad98b5ce308c707f74 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 13:53:59 +0000 Subject: [PATCH 04/31] refactor(libSceJobManager): read the timestamp and pause through portable helpers The worker's spin timing reads the counter through GuestCpu::ReadTsc and pauses through PreciseSpinPause. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceJobManager/Export.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libSceJobManager/Export.cpp b/core/libs/prx/libSceJobManager/Export.cpp index 8457fd2cd6a0..d8bf51486649 100644 --- a/core/libs/prx/libSceJobManager/Export.cpp +++ b/core/libs/prx/libSceJobManager/Export.cpp @@ -21,7 +21,8 @@ #include #include #include -#include +#include "prx/libc/include/GuestCpu.hpp" +#include "prx/libc/include/PreciseWait.hpp" #include #include #include @@ -821,18 +822,18 @@ constexpr std::uint32_t SpinPauses = 1024; void* APS5_VABI WorkerEntry(void* argument) { Manager& manager = *static_cast(argument); currentWorker = &manager; - std::uint64_t lastWork = __rdtsc(); + std::uint64_t lastWork = GuestCpu::ReadTsc(); std::unique_lock guard(manager.lock); while (manager.running) { if (RunOne(manager, guard)) { - lastWork = __rdtsc(); + lastWork = GuestCpu::ReadTsc(); continue; } - const bool spinning = manager.pendingInvocations != 0 && __rdtsc() - lastWork < manager.spinTicks; + const bool spinning = manager.pendingInvocations != 0 && GuestCpu::ReadTsc() - lastWork < manager.spinTicks; if (spinning) { const std::uint32_t epoch = manager.workEpoch.load(std::memory_order_acquire); guard.unlock(); - for (std::uint32_t pause = 0; pause != SpinPauses && manager.workEpoch.load(std::memory_order_acquire) == epoch; ++pause) _mm_pause(); + for (std::uint32_t pause = 0; pause != SpinPauses && manager.workEpoch.load(std::memory_order_acquire) == epoch; ++pause) PreciseSpinPause(); guard.lock(); continue; } @@ -840,7 +841,7 @@ void* APS5_VABI WorkerEntry(void* argument) { const std::uint32_t epoch = manager.workEpoch.load(std::memory_order_acquire); guard.unlock(); while (manager.workEpoch.load(std::memory_order_acquire) == epoch) { - for (std::uint32_t pause = 0; pause != SpinPauses; ++pause) _mm_pause(); + for (std::uint32_t pause = 0; pause != SpinPauses; ++pause) PreciseSpinPause(); } guard.lock(); continue; From 90c2dd1c979b94602d95a3c02a2e52912cee76fd Mon Sep 17 00:00:00 2001 From: oberonlift Date: Tue, 3 Nov 2026 16:06:27 +0000 Subject: [PATCH 05/31] refactor(libwebrtc): read the CPU features through GuestCpu webrtc's CPU feature query runs cpuid and xgetbv through new GuestCpu helpers, which issue the same instructions as . Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 16 +++++++++++ core/libs/prx/libwebrtc/src/CpuFeatures.cpp | 30 +++++---------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp index 20695cb270a4..53598c1a7b5b 100644 --- a/core/libs/prx/libc/include/GuestCpu.hpp +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -13,6 +13,22 @@ namespace GuestCpu { inline std::uint64_t ReadTsc() { return __rdtsc(); } +inline std::array Cpuid(const std::uint32_t leaf, const std::uint32_t subleaf) { + std::uint32_t eax; + std::uint32_t ebx; + std::uint32_t ecx; + std::uint32_t edx; + asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "0"(leaf), "2"(subleaf)); + return {eax, ebx, ecx, edx}; +} + +inline std::uint64_t Xgetbv(const std::uint32_t index) { + std::uint32_t low; + std::uint32_t high; + asm volatile("xgetbv" : "=a"(low), "=d"(high) : "c"(index)); + return (static_cast(high) << 32) | low; +} + inline std::uint32_t Mxcsr() { return _mm_getcsr(); } inline void SetMxcsr(const std::uint32_t value) { _mm_setcsr(value); } diff --git a/core/libs/prx/libwebrtc/src/CpuFeatures.cpp b/core/libs/prx/libwebrtc/src/CpuFeatures.cpp index 4033c443e6a3..40dc8de5f9dd 100644 --- a/core/libs/prx/libwebrtc/src/CpuFeatures.cpp +++ b/core/libs/prx/libwebrtc/src/CpuFeatures.cpp @@ -42,33 +42,21 @@ */ #include "prx/libwebrtc/libwebrtc.hpp" +#include "prx/libc/include/GuestCpu.hpp" -#include #include namespace { -std::uint64_t ExtendedControlRegister0() { - std::uint32_t low = 0; - std::uint32_t high = 0; - __asm__ volatile("xgetbv" : "=a"(low), "=d"(high) : "c"(0)); - return (static_cast(high) << 32) | low; -} - bool HasAvx2(unsigned int leaf1Ecx) { - unsigned int eax = 0; - unsigned int ebx = 0; - unsigned int ecx = 0; - unsigned int edx = 0; - __cpuid_count(0, 0, eax, ebx, ecx, edx); - if (static_cast(eax) < 7) + if (static_cast(GuestCpu::Cpuid(0, 0)[0]) < 7) return false; if ((leaf1Ecx & 0x10000000u) == 0 || (leaf1Ecx & 0x04000000u) == 0 || (leaf1Ecx & 0x08000000u) == 0) return false; - if ((ExtendedControlRegister0() & 0x6u) != 0x6u) + if ((GuestCpu::Xgetbv(0) & 0x6u) != 0x6u) return false; - __cpuid_count(7, 0, eax, ebx, ecx, edx); - return (ebx & 0x00000020u) != 0 && (ebx & 0x00000100u) != 0; + const auto leaf7 = GuestCpu::Cpuid(7, 0); + return (leaf7[1] & 0x00000020u) != 0 && (leaf7[1] & 0x00000100u) != 0; } } @@ -76,12 +64,8 @@ bool HasAvx2(unsigned int leaf1Ecx) { extern "C" { WebRtcAvailableCpuFeatures APS5_VABI _ZN6webrtc23GetAvailableCpuFeaturesEv_nid_postfix() { - unsigned int eax = 0; - unsigned int ebx = 0; - unsigned int ecx = 0; - unsigned int edx = 0; - __cpuid_count(1, 0, eax, ebx, ecx, edx); - return {(edx & 0x04000000u) != 0, HasAvx2(ecx), false}; + const auto leaf1 = GuestCpu::Cpuid(1, 0); + return {(leaf1[3] & 0x04000000u) != 0, HasAvx2(leaf1[2]), false}; } } From af1d810e26e87c8b6091b571cbf5ed216208c40d Mon Sep 17 00:00:00 2001 From: oberonlift Date: Mon, 2 Nov 2026 16:04:38 +0000 Subject: [PATCH 06/31] feat(ps5lift): add the runtime for lifted programs ps5lift's runtime is the library a lifted program links. It maps the guest images, dispatches indirect calls, lays out TLS as x86-64 does, runs x87 code on Berkeley SoftFloat, answers cpuid as the PS5's Zen 2, calls native functions through guest function pointers and back, and provides the GuestCpu functions, the guest FPU state and timestamp counter that AnyPS5's libraries read when they are built for a lifted guest. `tools/ps5lift.py runtime` builds it with SoftFloat, a new submodule, into a shared library for a target, and the program's main into an archive. Its known gaps are a ps5lift section of the technical debt. Co-Authored-By: Claude Opus 5.5 --- .gitmodules | 3 + 3rdparty/softfloat | 1 + docs/dev/TechnicalDebt.md | 21 + ps5lift/.gitignore | 3 + ps5lift/runtime/include/ps5lift/Bridge.hpp | 377 ++++++++++ ps5lift/runtime/include/ps5lift/State.h | 254 +++++++ ps5lift/runtime/softfloat_sources.txt | 322 +++++++++ ps5lift/runtime/src/Cpuid.cpp | 70 ++ ps5lift/runtime/src/GuestCpu.cpp | 271 +++++++ ps5lift/runtime/src/Jump.cpp | 77 ++ ps5lift/runtime/src/Main.cpp | 9 + ps5lift/runtime/src/NativeCall.cpp | 185 +++++ ps5lift/runtime/src/Runtime.cpp | 583 +++++++++++++++ ps5lift/runtime/src/Runtime.hpp | 22 + ps5lift/runtime/src/StringCompare.cpp | 99 +++ ps5lift/runtime/src/X87.cpp | 801 +++++++++++++++++++++ ps5lift/tools/ps5lift.py | 131 ++++ 17 files changed, 3229 insertions(+) create mode 160000 3rdparty/softfloat create mode 100644 ps5lift/.gitignore create mode 100644 ps5lift/runtime/include/ps5lift/Bridge.hpp create mode 100644 ps5lift/runtime/include/ps5lift/State.h create mode 100644 ps5lift/runtime/softfloat_sources.txt create mode 100644 ps5lift/runtime/src/Cpuid.cpp create mode 100644 ps5lift/runtime/src/GuestCpu.cpp create mode 100644 ps5lift/runtime/src/Jump.cpp create mode 100644 ps5lift/runtime/src/Main.cpp create mode 100644 ps5lift/runtime/src/NativeCall.cpp create mode 100644 ps5lift/runtime/src/Runtime.cpp create mode 100644 ps5lift/runtime/src/Runtime.hpp create mode 100644 ps5lift/runtime/src/StringCompare.cpp create mode 100644 ps5lift/runtime/src/X87.cpp create mode 100644 ps5lift/tools/ps5lift.py diff --git a/.gitmodules b/.gitmodules index 34556e3e220c..a51c7bb143fd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -79,3 +79,6 @@ [submodule "3rdparty/sqlite"] path = 3rdparty/sqlite url = https://github.com/sqlite/sqlite.git +[submodule "3rdparty/softfloat"] + path = 3rdparty/softfloat + url = https://github.com/ucb-bar/berkeley-softfloat-3 diff --git a/3rdparty/softfloat b/3rdparty/softfloat new file mode 160000 index 000000000000..0a5ad290c6b2 --- /dev/null +++ b/3rdparty/softfloat @@ -0,0 +1 @@ +Subproject commit 0a5ad290c6b287f6605bb99a35ad0842c1e61aa5 diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 228daf8e40bc..bf24b18e8d41 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -29,3 +29,24 @@ Throughout the project, every function at every stage either **does exactly what - [Quick syntax checks](../../tools/check_compile.py) do not support Windows-style response-file quoting. GNU response files reject empty options, byte order marks, quoted CRLF, Windows text-mode EOF markers and escapes whose meanings differ between GCC and Clang, including backslashes in single quotes, most backslashes in double quotes and escaped line endings. Supply expanded compile arguments for those forms; the Ninja fallback uses `ninja -t compdb -x`. - [Quick syntax checks](../../tools/check_compile.py) do not model every compiler option's operand count. A preprocessing mode following an unrecognized option without a joined value is rejected rather than risk removing that option's operand. Compiler wrappers with their own option grammar are not covered, except a leading `cmake -E env`, whose options other than `NAME=VALUE`, `--unset=`, `--modify` and `--` are rejected. Forwarded frontend actions (`-Xclang`, `-Xpreprocessor` and their joined forms) are rejected. Comma-forwarded options (`-Wp,`) allow only recognized macro/include operands; response-file tokens are rejected even as operands, and other options are rejected because unmodeled actions or output operands could bypass syntax analysis or write outputs. See the [Clang option grammar](https://clang.llvm.org/docs/ClangCommandLineReference.html). - [Quick syntax checks](../../tools/check_compile.py) reject `-save-temps=` and `--save-temps=` values other than `cwd` and `obj` instead of passing them to the compiler: an unknown value fails on GCC, while newer Clang builds take any value as an output prefix and write preprocessed files even under `-fsyntax-only` (measured on Apple clang 21), so a passed-through mode either fails the check on one compiler or silently writes outputs on the other. + +### ps5lift + +#### Bugs + +Each of these gives a wrong result, corrupts state or reports a misleading error instead of stopping cleanly, and most have no fixture that would catch them. + +- Unnormals, pseudo-NaNs and pseudo-infinities are computed as SoftFloat normalises them, instead of raising IE and returning the indefinite; `fxam` already classifies them as unsupported. +- [`PS5LIFT_MXCSR_MASK`](../../ps5lift/runtime/include/ps5lift/State.h) is 0xffff where Zen 2's MXCSR mask is 0x2ffff: the PS5's `cpuid` reports misaligned SSE, which allows MXCSR bit 17, and `ldmxcsr` and `fxrstor` with bit 17 set stop the program instead of taking the value. + +#### Gaps + +- [The runtime](../../ps5lift/runtime/src/Runtime.cpp) maps the whole image read-write (through AnyPS5's guest arena when its libc is linked in), and runs modules' initializers but not the executable's nor any finalizer; the relinker's entry stub is the only entry contract tested. +- [`cpuid` and `xgetbv`](../../ps5lift/runtime/src/Cpuid.cpp) answer as an AMD Zen 2: with the signature of AMD's 4700S desktop kit, which is built on the PS5's chip (family 17h, model 47h, stepping 0), and with the user-mode extensions LLVM enables for `znver2` plus the optional ones AnyPS5's host check lists for the console. None of it is measured on a console. The highest leaves reported are 0xd and 0x80000001, so code reads the cache, topology and brand-string leaves as absent; leaves 5 and 6 and `xgetbv` of a register other than XCR0 stop the program. +- Lifted calls nest on the host stack, which the runtime sizes at 512 MiB for every guest thread, those libkernel starts included; a native thread that calls guest code has the host stack its creator gave it. +- `longjmp` unwinds lifted frames ([Jump.cpp](../../ps5lift/runtime/src/Jump.cpp)) but not native frames between them, so a `longjmp` out of a guest callback that native code called stops the program. Its target frame is the innermost one with a `setjmp` call whose entry stack pointer is at or above the saved one, which assumes the frame did not move its stack pointer above that value after `setjmp`. +- Native calls through a guest function pointer read from memory pass only the arguments and results the host keeps in registers, cost a signal each, and the x86-64 host's trampoline has not run. [Callbacks](../../ps5lift/runtime/include/ps5lift/Bridge.hpp) take and return scalars only, and each signature has 256 thunks; the 257th distinct guest address for one signature stops the program. The guest state a foreign native thread gets on its first callback lives until that thread exits. +- An SSE exception that MXCSR unmasks raises no SIGFPE, where the console raises one: [`ps5lift_apply_mxcsr`](../../ps5lift/runtime/src/Runtime.cpp) gives the host FPU MXCSR's rounding mode, DAZ and FTZ but never its exception masks, for lifted code's `ldmxcsr` and a lifted libc's `feenableexcept` alike. +- A binary128 `long double` from host code reaches the guest through `GuestCpuStoreExtended` and the bridges' `ReturnExtended` rounded once more, to the x87 format's 64-bit significand in the control word's rounding mode ([X87.cpp](../../ps5lift/runtime/src/X87.cpp), `ps5lift_extended_from_host`), so a result that host math already rounded to binary128 is rounded twice, which can be off by one in the last place, and the precision control is ignored: a guest that sets it to 24 or 53 bits still gets 64. +- [GuestCpu](../../ps5lift/runtime/src/GuestCpu.cpp), the guest FPU state that a lifted AnyPS5 libc reads: x87 is only its control and status words, so `fegetenv` reports every x87 register empty; `fwait` with an unmasked pending x87 exception ends the program where the console raises SIGFPE; the MXCSR support mask is reported as `0xffff`, not measured on a PS5. Flags that lifted arithmetic raises reach `fetestexcept` through the host FPU, but LLVM may reorder or fold floating-point operations around a call because it assumes the default environment. +- [x87](../../ps5lift/runtime/src/X87.cpp): the transcendental instructions (`fsin`, `fcos`, `fsincos`, `fptan`, `fpatan`, `f2xm1`, `fyl2x`, `fyl2xp1`), `fxtract`, `fbld`, `fbstp`, `fsave`, `frstor`, `fnop` and `ffreep` are reported as unsupported. `fprem` always finishes the reduction in one step, so C2 is never set, and the C bits of an `fprem` with a NaN result and the rest of the x87 behaviour were measured on an Intel Xeon, not on Zen 2. An unmasked x87 exception ends the program at the next `fwait`, not at the next x87 instruction, and `fnstenv`, `fxsave` and `fxsave64` record zero instruction and operand pointers and last opcode. `fxsave` writes the first 416 bytes of its area and leaves the reserved rest alone; the `fx-state` fixture matched an Intel Pentium Silver (Goldmont Plus) for the words, MXCSR and its mask, the loaded registers and an XMM register. diff --git a/ps5lift/.gitignore b/ps5lift/.gitignore new file mode 100644 index 000000000000..ef66a3a2a521 --- /dev/null +++ b/ps5lift/.gitignore @@ -0,0 +1,3 @@ +/build*/ +__pycache__/ +/.cache/ diff --git a/ps5lift/runtime/include/ps5lift/Bridge.hpp b/ps5lift/runtime/include/ps5lift/Bridge.hpp new file mode 100644 index 000000000000..3c31db6063a2 --- /dev/null +++ b/ps5lift/runtime/include/ps5lift/Bridge.hpp @@ -0,0 +1,377 @@ +#ifndef PS5LIFT_BRIDGE_HPP +#define PS5LIFT_BRIDGE_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ps5Lift::Bridge { + +struct GuestUnwind {}; + +constexpr int IntegerRegisters[6] = {PS5LIFT_RDI, PS5LIFT_RSI, PS5LIFT_RDX, PS5LIFT_RCX, PS5LIFT_R8, PS5LIFT_R9}; + +template +T FromBits(const std::uint64_t bits) { + if constexpr (std::is_pointer_v) { + return reinterpret_cast(bits); + } else if constexpr (std::is_same_v) { + return (bits & 0xff) != 0; + } else if constexpr (std::is_floating_point_v) { + T value; + std::memcpy(&value, &bits, sizeof(T)); + return value; + } else { + return static_cast(bits); + } +} + +template +T Integer(const Ps5LiftState* state, const int index) { + return FromBits(state->Gpr[IntegerRegisters[index]]); +} + +template +T Floating(const Ps5LiftState* state, const int index) { + std::uint64_t bits = 0; + std::memcpy(&bits, state->Vector[index], sizeof(T)); + return FromBits(bits); +} + +template +T Stack(const Ps5LiftState* state, const int slot) { + std::uint64_t bits = 0; + std::memcpy(&bits, reinterpret_cast(state->Gpr[PS5LIFT_RSP] + 8 + 8 * static_cast(slot)), sizeof(bits)); + return FromBits(bits); +} + +template +T Integer128(const Ps5LiftState* state, const int index) { + const auto low = static_cast(state->Gpr[IntegerRegisters[index]]); + const auto high = static_cast(state->Gpr[IntegerRegisters[index + 1]]); + return static_cast(low | high << 64); +} + +template +T Stack128(const Ps5LiftState* state, const int slot) { + unsigned __int128 value = 0; + std::memcpy(&value, reinterpret_cast(state->Gpr[PS5LIFT_RSP] + 8 + 8 * static_cast(slot)), sizeof(value)); + return static_cast(value); +} + +enum class Eightbyte { Integer, Sse }; + +template +T Record(const Eightbytes... eightbytes) { + static_assert(std::is_trivially_copyable_v && sizeof(T) <= 8 * sizeof...(Eightbytes) && sizeof(T) > 8 * (sizeof...(Eightbytes) - 1)); + const std::uint64_t bits[] = {eightbytes...}; + T value; + std::memcpy(&value, bits, sizeof(T)); + return value; +} + +template +T StackRecord(const Ps5LiftState* state, const int slot) { + static_assert(std::is_trivially_copyable_v); + T value; + std::memcpy(&value, reinterpret_cast(state->Gpr[PS5LIFT_RSP] + 8 + 8 * static_cast(slot)), sizeof(T)); + return value; +} + +template +void ReturnRecord(Ps5LiftState* state, const T& value) { + static_assert(std::is_trivially_copyable_v && sizeof(T) <= 8 * sizeof...(Classes) && sizeof(T) > 8 * (sizeof...(Classes) - 1)); + constexpr Eightbyte classes[] = {Classes...}; + std::uint64_t bits[sizeof...(Classes)] = {}; + std::memcpy(bits, &value, sizeof(T)); + constexpr int IntegerResults[] = {PS5LIFT_RAX, PS5LIFT_RDX}; + int integers = 0; + int vectors = 0; + for (std::size_t index = 0; index < sizeof...(Classes); ++index) { + if (classes[index] == Eightbyte::Integer) { + state->Gpr[IntegerResults[integers++]] = bits[index]; + } else { + std::memset(state->Vector[vectors], 0, 16); + std::memcpy(state->Vector[vectors++], &bits[index], 8); + } + } +} + +template +void ReturnIndirect(Ps5LiftState* state, const std::uint64_t address, const T& value) { + static_assert(std::is_trivially_copyable_v); + std::memcpy(reinterpret_cast(address), &value, sizeof(T)); + state->Gpr[PS5LIFT_RAX] = address; +} + +class GuestEntry { +public: + GuestEntry() : _state(ps5lift_enter_guest()) {} + + ~GuestEntry() { ps5lift_leave_guest(); } + + GuestEntry(const GuestEntry&) = delete; + GuestEntry& operator=(const GuestEntry&) = delete; + + Ps5LiftState* State() const { return _state; } + +private: + Ps5LiftState* _state; +}; + +class alignas(16) LongDouble { +public: + LongDouble() = default; + + friend LongDouble operator/(const LongDouble& dividend, const LongDouble& divisor) { + LongDouble quotient; + ps5lift_extended_operation(ps5lift_current_state(), PS5LIFT_X87_DIVIDE, dividend._bytes, divisor._bytes, quotient._bytes); + return quotient; + } + +private: + unsigned char _bytes[16] = {}; +}; + +inline const void* ExtendedSlot(const Ps5LiftState* state, const int slot) { + return reinterpret_cast(state->Gpr[PS5LIFT_RSP] + 8 + 8 * static_cast(slot)); +} + +template +T Extended(const Ps5LiftState* state, const int slot) { + static_assert(sizeof(T) == 16 && std::is_trivially_copyable_v, "a guest long double is 16 bytes holding x87's 80 bits"); + T value{}; + std::memcpy(&value, ExtendedSlot(state, slot), 10); + return value; +} + +template +void ReturnExtended(Ps5LiftState* state, const T& value) { + static_assert(sizeof(T) == 16 && std::is_trivially_copyable_v, "a guest long double is 16 bytes holding x87's 80 bits"); + ps5lift_x87(state, PS5LIFT_X87_LOAD, PS5LIFT_X87_FLOAT80, reinterpret_cast(&value), 0); +} + +#ifdef PS5LIFT_HOST_LONG_DOUBLE_HOLDS_EXTENDED +class ExtendedScope { +public: + explicit ExtendedScope(const Ps5LiftState* state) : _saved(std::fegetround()) { + static constexpr int Rounding[4] = {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}; + std::fesetround(Rounding[(state->X87Control >> 10) & 3]); + } + + ~ExtendedScope() { std::fesetround(_saved); } + + ExtendedScope(const ExtendedScope&) = delete; + ExtendedScope& operator=(const ExtendedScope&) = delete; + +private: + int _saved; +}; + +inline long double HostExtended(const Ps5LiftState* state, const int slot) { + long double value; + ps5lift_extended_to_host(ExtendedSlot(state, slot), &value); + return value; +} + +inline void ReturnExtended(Ps5LiftState* state, const long double value) { + unsigned char extended[16] = {}; + ps5lift_extended_from_host(state, &value, extended); + ps5lift_x87(state, PS5LIFT_X87_LOAD, PS5LIFT_X87_FLOAT80, reinterpret_cast(extended), 0); +} +#endif + +class VariadicArguments { +public: + VariadicArguments(const Ps5LiftState* state, const int integers, const int floats, const int stack) { + for (int index = 0; index < 6; ++index) std::memcpy(_saveArea + 8 * index, &state->Gpr[IntegerRegisters[index]], 8); + for (int index = 0; index < 8; ++index) std::memcpy(_saveArea + 48 + 16 * index, state->Vector[index], 16); + _list.GpOffset = static_cast(8 * integers); + _list.FpOffset = static_cast(48 + 16 * floats); + _list.OverflowArea = state->Gpr[PS5LIFT_RSP] + 8 + 8 * static_cast(stack); + _list.SaveArea = reinterpret_cast(_saveArea); + } + + VariadicArguments(const VariadicArguments&) = delete; + VariadicArguments& operator=(const VariadicArguments&) = delete; + + void* List() { return &_list; } + +private: + struct { + std::uint32_t GpOffset; + std::uint32_t FpOffset; + std::uint64_t OverflowArea; + std::uint64_t SaveArea; + } _list; + alignas(16) unsigned char _saveArea[176]; +}; + +template +void Return(Ps5LiftState* state, const T value) { + if constexpr (std::is_same_v || std::is_same_v) { + const auto bits = static_cast(value); + state->Gpr[PS5LIFT_RAX] = static_cast(bits); + state->Gpr[PS5LIFT_RDX] = static_cast(bits >> 64); + } else if constexpr (std::is_floating_point_v) { + std::memset(state->Vector[0], 0, 16); + std::memcpy(state->Vector[0], &value, sizeof(T)); + } else if constexpr (std::is_pointer_v) { + state->Gpr[PS5LIFT_RAX] = reinterpret_cast(value); + } else if constexpr (std::is_signed_v) { + state->Gpr[PS5LIFT_RAX] = static_cast(static_cast(value)); + } else { + state->Gpr[PS5LIFT_RAX] = static_cast(value); + } +} + +template +constexpr bool IsFloating = std::is_floating_point_v; + +template +std::uint64_t ToBits(const T value) { + if constexpr (std::is_pointer_v) { + return reinterpret_cast(value); + } else if constexpr (std::is_floating_point_v) { + std::uint64_t bits = 0; + std::memcpy(&bits, &value, sizeof(T)); + return bits; + } else if constexpr (std::is_signed_v) { + return static_cast(static_cast(value)); + } else { + return static_cast(value); + } +} + +template +R GuestResult(const Ps5LiftState* state) { + if constexpr (std::is_void_v) { + return; + } else if constexpr (std::is_floating_point_v) { + return Floating(state, 0); + } else { + return FromBits(state->Gpr[PS5LIFT_RAX]); + } +} + +inline void RunGuest(Ps5LiftState* state, const std::uint64_t target, const std::uint64_t* stackArguments, const int stackCount, const int vectors) { + const auto savedRsp = state->Gpr[PS5LIFT_RSP]; + auto rsp = (savedRsp - 8 * static_cast(stackCount)) & ~std::uint64_t{15}; + std::memcpy(reinterpret_cast(rsp), stackArguments, 8 * static_cast(stackCount)); + rsp -= 8; + constexpr std::uint64_t ReturnSentinel = 0; + std::memcpy(reinterpret_cast(rsp), &ReturnSentinel, sizeof(ReturnSentinel)); + state->Gpr[PS5LIFT_RSP] = rsp; + state->Gpr[PS5LIFT_RAX] = static_cast(vectors); + ps5lift_dispatch(state, target); + if (state->Unwinding) ps5lift_fail("a longjmp or exception in guest code that native code called leaves through native code for 0x%" PRIx64, state->ResumeRip); + state->Gpr[PS5LIFT_RSP] = savedRsp; +} + +template +R CallGuest(const std::uint64_t target, Args... arguments) { + static_assert(((std::is_arithmetic_v || std::is_pointer_v || std::is_enum_v) && ...), "callbacks take scalar arguments"); + const GuestEntry entry; + auto* state = entry.State(); + std::uint64_t stackArguments[sizeof...(Args) + 1]{}; + int stackCount = 0; + int integers = 0; + int floats = 0; + const auto place = [&](const auto value) { + using T = std::decay_t; + const auto bits = ToBits(value); + if constexpr (IsFloating) { + if (floats < 8) { + std::memset(state->Vector[floats], 0, 16); + std::memcpy(state->Vector[floats++], &bits, sizeof(T)); + return; + } + } else { + if (integers < 6) { + state->Gpr[IntegerRegisters[integers++]] = bits; + return; + } + } + stackArguments[stackCount++] = bits; + }; + (place(arguments), ...); + RunGuest(state, target, stackArguments, stackCount, floats); + if constexpr (!std::is_void_v) return GuestResult(state); +} + +inline bool SentinelFunction(const std::uint64_t value) { + return value < 4096 || value == ~std::uint64_t{0}; +} + +template +struct CallbackPool; + +template +struct CallbackPool { + static constexpr std::size_t Capacity = 256; + using Native = R (*)(Args...); + + static inline std::atomic Targets[Capacity]{}; + static inline std::atomic Count{0}; + static inline std::mutex Lock; + + template + static R Thunk(Args... arguments) { + return CallGuest(Targets[Index].load(std::memory_order_acquire), arguments...); + } + + template + static constexpr std::array MakeThunks(std::index_sequence) { + return {&Thunk...}; + } + + static inline constexpr std::array Thunks = MakeThunks(std::make_index_sequence{}); + + static Native For(const std::uint64_t target) { + if (SentinelFunction(target)) return reinterpret_cast(target); + const auto count = Count.load(std::memory_order_acquire); + for (std::size_t i = 0; i < count; ++i) { + if (Targets[i].load(std::memory_order_relaxed) == target) return Thunks[i]; + } + std::lock_guard guard(Lock); + const auto current = Count.load(std::memory_order_relaxed); + for (std::size_t i = count; i < current; ++i) { + if (Targets[i].load(std::memory_order_relaxed) == target) return Thunks[i]; + } + if (current == Capacity) ps5lift_fail("guest function 0x%" PRIx64 " needs a callback thunk, but its signature's %zu are taken", target, Capacity); + Targets[current].store(target, std::memory_order_release); + Count.store(current + 1, std::memory_order_release); + return Thunks[current]; + } + + static std::uint64_t Guest(const Native native) { + const auto count = Count.load(std::memory_order_acquire); + for (std::size_t i = 0; i < count; ++i) { + if (Thunks[i] == native) return Targets[i].load(std::memory_order_relaxed); + } + return reinterpret_cast(native); + } +}; + +template +auto Callback(const std::uint64_t target) { + return CallbackPool::For(target); +} + +template +std::uint64_t GuestFunction(Signature* const native) { + return CallbackPool::Guest(native); +} + +} + +#endif diff --git a/ps5lift/runtime/include/ps5lift/State.h b/ps5lift/runtime/include/ps5lift/State.h new file mode 100644 index 000000000000..a5495c8626cd --- /dev/null +++ b/ps5lift/runtime/include/ps5lift/State.h @@ -0,0 +1,254 @@ +#ifndef PS5LIFT_STATE_H +#define PS5LIFT_STATE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum Ps5LiftGpr { + PS5LIFT_RAX, + PS5LIFT_RCX, + PS5LIFT_RDX, + PS5LIFT_RBX, + PS5LIFT_RSP, + PS5LIFT_RBP, + PS5LIFT_RSI, + PS5LIFT_RDI, + PS5LIFT_R8, + PS5LIFT_R9, + PS5LIFT_R10, + PS5LIFT_R11, + PS5LIFT_R12, + PS5LIFT_R13, + PS5LIFT_R14, + PS5LIFT_R15, + PS5LIFT_GPR_COUNT +}; + +enum Ps5LiftFlag { + PS5LIFT_CF, + PS5LIFT_PF, + PS5LIFT_AF, + PS5LIFT_ZF, + PS5LIFT_SF, + PS5LIFT_OF, + PS5LIFT_DF, + PS5LIFT_FLAG_COUNT +}; + +typedef struct Ps5LiftState { + uint64_t Gpr[PS5LIFT_GPR_COUNT]; + uint64_t Rip; + uint64_t FsBase; + uint64_t GsBase; + uint8_t Flags[PS5LIFT_FLAG_COUNT]; + uint8_t Padding[1]; + uint32_t Mxcsr; + uint16_t X87Control; + uint16_t X87Status; + uint8_t X87Valid; + uint8_t X87Padding[7]; + uint8_t X87Registers[8][16]; + uint8_t Vector[16][32]; + uint64_t ResumeRip; + uint64_t ResumeRsp; + uint8_t Unwinding; + uint8_t UnwindPadding[7]; +} Ps5LiftState; + +enum Ps5LiftX87Operation { + PS5LIFT_X87_LOAD, + PS5LIFT_X87_STORE, + PS5LIFT_X87_ADD, + PS5LIFT_X87_SUBTRACT, + PS5LIFT_X87_SUBTRACT_REVERSE, + PS5LIFT_X87_MULTIPLY, + PS5LIFT_X87_DIVIDE, + PS5LIFT_X87_DIVIDE_REVERSE, + PS5LIFT_X87_COMPARE, + PS5LIFT_X87_COMPARE_UNORDERED, + PS5LIFT_X87_COMPARE_FLAGS, + PS5LIFT_X87_COMPARE_FLAGS_UNORDERED, + PS5LIFT_X87_CHANGE_SIGN, + PS5LIFT_X87_ABSOLUTE, + PS5LIFT_X87_SQUARE_ROOT, + PS5LIFT_X87_ROUND, + PS5LIFT_X87_SCALE, + PS5LIFT_X87_PARTIAL_REMAINDER, + PS5LIFT_X87_IEEE_REMAINDER, + PS5LIFT_X87_EXAMINE, + PS5LIFT_X87_TEST, + PS5LIFT_X87_EXCHANGE, + PS5LIFT_X87_FREE, + PS5LIFT_X87_INCREMENT_TOP, + PS5LIFT_X87_DECREMENT_TOP, + PS5LIFT_X87_INITIALIZE, + PS5LIFT_X87_CLEAR_EXCEPTIONS, + PS5LIFT_X87_WAIT, + PS5LIFT_X87_STORE_STATUS, + PS5LIFT_X87_STORE_CONTROL, + PS5LIFT_X87_LOAD_CONTROL, + PS5LIFT_X87_STORE_ENVIRONMENT, + PS5LIFT_X87_LOAD_ENVIRONMENT, + PS5LIFT_X87_LOAD_CONSTANT, + PS5LIFT_X87_MOVE_REGISTER, + PS5LIFT_X87_SAVE_STATE, + PS5LIFT_X87_RESTORE_STATE +}; + +enum Ps5LiftX87Format { + PS5LIFT_X87_REGISTER = 0, + PS5LIFT_X87_FLOAT32 = 1, + PS5LIFT_X87_FLOAT64 = 2, + PS5LIFT_X87_FLOAT80 = 3, + PS5LIFT_X87_INTEGER16 = 4, + PS5LIFT_X87_INTEGER32 = 5, + PS5LIFT_X87_INTEGER64 = 6, + PS5LIFT_X87_TRUNCATE = 8, + PS5LIFT_X87_POP = 16, + PS5LIFT_X87_POP_TWICE = 32, + PS5LIFT_X87_TO_REGISTER = 64 +}; + +enum Ps5LiftX87Constant { + PS5LIFT_X87_CONSTANT_ONE = 0, + PS5LIFT_X87_CONSTANT_LOG2_10 = 1, + PS5LIFT_X87_CONSTANT_LOG2_E = 2, + PS5LIFT_X87_CONSTANT_PI = 3, + PS5LIFT_X87_CONSTANT_LOG10_2 = 4, + PS5LIFT_X87_CONSTANT_LN_2 = 5, + PS5LIFT_X87_CONSTANT_ZERO = 6 +}; + +enum Ps5LiftX87Comparison { + PS5LIFT_X87_GREATER = 0, + PS5LIFT_X87_LESS = 1, + PS5LIFT_X87_EQUAL = 2, + PS5LIFT_X87_UNORDERED = 3 +}; + +enum Ps5LiftStringOperation { + PS5LIFT_STRING_MOVS = 0, + PS5LIFT_STRING_STOS = 1 +}; + +enum Ps5LiftStringCompare { + PS5LIFT_STRING_IMPLICIT = 1 << 8, + PS5LIFT_STRING_SHORT_SECOND = 1 << 16, + PS5LIFT_STRING_SHORT_FIRST = 1 << 17 +}; + +typedef void (*Ps5LiftFunction)(Ps5LiftState*); + +typedef struct Ps5LiftFunctionEntry { + uint64_t GuestAddress; + Ps5LiftFunction Host; +} Ps5LiftFunctionEntry; + +typedef struct Ps5LiftSegment { + uint64_t GuestAddress; + uint64_t MemorySize; + uint64_t FileSize; + const uint8_t* Data; + uint32_t Protection; +} Ps5LiftSegment; + +typedef struct Ps5LiftDataImport { + uint64_t Slot; + void* const* Address; + int64_t Addend; +} Ps5LiftDataImport; + +typedef struct Ps5LiftImport { + uint64_t GuestAddress; + const char* Name; +} Ps5LiftImport; + +typedef struct Ps5LiftExport { + const char* Name; + uint64_t Address; +} Ps5LiftExport; + +typedef struct Ps5LiftImage { + const char* Name; + uint64_t LoadBias; + uint64_t ProgramHeaders; + uint64_t ProgramHeaderCount; + const Ps5LiftSegment* Segments; + uint64_t SegmentCount; + uint64_t TlsModule; + uint64_t TlsAddress; + uint64_t TlsFileSize; + uint64_t TlsMemorySize; + uint64_t TlsAlignment; + uint64_t TlsOffset; + const Ps5LiftDataImport* DataImports; + uint64_t DataImportCount; + const Ps5LiftImport* Imports; + uint64_t ImportCount; + uint64_t Init; + uint64_t InitArray; + uint64_t InitCount; + const Ps5LiftExport* Exports; + uint64_t ExportCount; +} Ps5LiftImage; + +typedef struct Ps5LiftProgram { + const Ps5LiftImage* Images; + uint64_t ImageCount; + const uint64_t* Initialization; + uint64_t InitializationCount; + const Ps5LiftFunctionEntry* Functions; + uint64_t FunctionCount; + uint64_t Entry; +} Ps5LiftProgram; + +void ps5lift_use_program(const Ps5LiftProgram* program); + +void ps5lift_dispatch(Ps5LiftState* state, uint64_t target); +Ps5LiftFunction ps5lift_resolve(uint64_t target); +#define PS5LIFT_MXCSR_MASK 0xffffu + +void ps5lift_apply_mxcsr(Ps5LiftState* state); +uint32_t ps5lift_read_mxcsr(Ps5LiftState* state); +uint32_t ps5lift_x87(Ps5LiftState* state, uint32_t operation, uint32_t format, uint64_t address, uint32_t index); +void ps5lift_extended_operation(Ps5LiftState* state, uint32_t operation, const void* left, const void* right, void* result); +void ps5lift_extended_load(Ps5LiftState* state, uint32_t format, const void* source, void* result); +void ps5lift_extended_store(Ps5LiftState* state, uint32_t format, const void* value, void* destination); +#if LDBL_MANT_DIG == 64 || LDBL_MANT_DIG == 113 +#define PS5LIFT_HOST_LONG_DOUBLE_HOLDS_EXTENDED +void ps5lift_extended_to_host(const void* extended, long double* host); +void ps5lift_extended_from_host(const Ps5LiftState* state, const long double* host, void* extended); +#endif +Ps5LiftState* ps5lift_current_state(void); +void ps5lift_use_guest_stack(uint64_t stack_top); +uint64_t ps5lift_interrupt_stack(void); +uint64_t ps5lift_host_stack_size(void); +Ps5LiftState* ps5lift_enter_guest(void); +void ps5lift_leave_guest(void); +void ps5lift_run_main(void); +uint64_t ps5lift_read_tsc(void); +uint64_t ps5lift_random(void); +uint32_t ps5lift_processor_id(void); +void ps5lift_flush_cache_line(uint64_t address); +void ps5lift_write_back_cache_line(uint64_t address); +void ps5lift_cpuid(Ps5LiftState* state, uint64_t address); +uint64_t ps5lift_xgetbv(uint64_t address, uint32_t index); +[[noreturn, gnu::format(printf, 1, 2)]] void ps5lift_fail(const char* format, ...); +void ps5lift_unsupported(Ps5LiftState* state, uint64_t address, const char* mnemonic); +void ps5lift_unknown_resume(Ps5LiftState* state, uint64_t function, uint64_t resume); +void ps5lift_trap(Ps5LiftState* state, uint64_t address); +void ps5lift_left_jump_table(Ps5LiftState* state, uint64_t address); +void ps5lift_interrupt(Ps5LiftState* state, uint64_t address, uint32_t vector); +void ps5lift_divide_error(Ps5LiftState* state, uint64_t address); +void ps5lift_string_op(Ps5LiftState* state, uint32_t operation, uint32_t width, uint32_t repeat, uint64_t source_base); +uint32_t ps5lift_string_compare(uint64_t first_low, uint64_t first_high, uint64_t second_low, uint64_t second_high, int64_t first_length, int64_t second_length, uint32_t control); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ps5lift/runtime/softfloat_sources.txt b/ps5lift/runtime/softfloat_sources.txt new file mode 100644 index 000000000000..635f764c4455 --- /dev/null +++ b/ps5lift/runtime/softfloat_sources.txt @@ -0,0 +1,322 @@ +3rdparty/softfloat/source/8086-SSE/extF80M_isSignalingNaN.c +3rdparty/softfloat/source/8086-SSE/f128M_isSignalingNaN.c +3rdparty/softfloat/source/8086-SSE/s_bf16UIToCommonNaN.c +3rdparty/softfloat/source/8086-SSE/s_commonNaNToBF16UI.c +3rdparty/softfloat/source/8086-SSE/s_commonNaNToExtF80UI.c +3rdparty/softfloat/source/8086-SSE/s_commonNaNToF128UI.c +3rdparty/softfloat/source/8086-SSE/s_commonNaNToF16UI.c +3rdparty/softfloat/source/8086-SSE/s_commonNaNToF32UI.c +3rdparty/softfloat/source/8086-SSE/s_commonNaNToF64UI.c +3rdparty/softfloat/source/8086-SSE/s_extF80UIToCommonNaN.c +3rdparty/softfloat/source/8086-SSE/s_f128UIToCommonNaN.c +3rdparty/softfloat/source/8086-SSE/s_f16UIToCommonNaN.c +3rdparty/softfloat/source/8086-SSE/s_f32UIToCommonNaN.c +3rdparty/softfloat/source/8086-SSE/s_f64UIToCommonNaN.c +3rdparty/softfloat/source/8086-SSE/s_propagateNaNExtF80UI.c +3rdparty/softfloat/source/8086-SSE/s_propagateNaNF128UI.c +3rdparty/softfloat/source/8086-SSE/s_propagateNaNF16UI.c +3rdparty/softfloat/source/8086-SSE/s_propagateNaNF32UI.c +3rdparty/softfloat/source/8086-SSE/s_propagateNaNF64UI.c +3rdparty/softfloat/source/8086-SSE/softfloat_raiseFlags.c +3rdparty/softfloat/source/bf16_add.c +3rdparty/softfloat/source/bf16_div.c +3rdparty/softfloat/source/bf16_eq.c +3rdparty/softfloat/source/bf16_isSignalingNaN.c +3rdparty/softfloat/source/bf16_le.c +3rdparty/softfloat/source/bf16_lt.c +3rdparty/softfloat/source/bf16_mul.c +3rdparty/softfloat/source/bf16_mulAdd.c +3rdparty/softfloat/source/bf16_sqrt.c +3rdparty/softfloat/source/bf16_sub.c +3rdparty/softfloat/source/bf16_to_f32.c +3rdparty/softfloat/source/bf16_to_f64.c +3rdparty/softfloat/source/bf16_to_i32.c +3rdparty/softfloat/source/bf16_to_ui32.c +3rdparty/softfloat/source/extF80M_add.c +3rdparty/softfloat/source/extF80M_div.c +3rdparty/softfloat/source/extF80M_eq.c +3rdparty/softfloat/source/extF80M_eq_signaling.c +3rdparty/softfloat/source/extF80M_le.c +3rdparty/softfloat/source/extF80M_le_quiet.c +3rdparty/softfloat/source/extF80M_lt.c +3rdparty/softfloat/source/extF80M_lt_quiet.c +3rdparty/softfloat/source/extF80M_mul.c +3rdparty/softfloat/source/extF80M_rem.c +3rdparty/softfloat/source/extF80M_roundToInt.c +3rdparty/softfloat/source/extF80M_sqrt.c +3rdparty/softfloat/source/extF80M_sub.c +3rdparty/softfloat/source/extF80M_to_f128M.c +3rdparty/softfloat/source/extF80M_to_f16.c +3rdparty/softfloat/source/extF80M_to_f32.c +3rdparty/softfloat/source/extF80M_to_f64.c +3rdparty/softfloat/source/extF80M_to_i32.c +3rdparty/softfloat/source/extF80M_to_i32_r_minMag.c +3rdparty/softfloat/source/extF80M_to_i64.c +3rdparty/softfloat/source/extF80M_to_i64_r_minMag.c +3rdparty/softfloat/source/extF80M_to_ui32.c +3rdparty/softfloat/source/extF80M_to_ui32_r_minMag.c +3rdparty/softfloat/source/extF80M_to_ui64.c +3rdparty/softfloat/source/extF80M_to_ui64_r_minMag.c +3rdparty/softfloat/source/extF80_add.c +3rdparty/softfloat/source/extF80_div.c +3rdparty/softfloat/source/extF80_eq.c +3rdparty/softfloat/source/extF80_eq_signaling.c +3rdparty/softfloat/source/extF80_isSignalingNaN.c +3rdparty/softfloat/source/extF80_le.c +3rdparty/softfloat/source/extF80_le_quiet.c +3rdparty/softfloat/source/extF80_lt.c +3rdparty/softfloat/source/extF80_lt_quiet.c +3rdparty/softfloat/source/extF80_mul.c +3rdparty/softfloat/source/extF80_rem.c +3rdparty/softfloat/source/extF80_roundToInt.c +3rdparty/softfloat/source/extF80_sqrt.c +3rdparty/softfloat/source/extF80_sub.c +3rdparty/softfloat/source/extF80_to_f128.c +3rdparty/softfloat/source/extF80_to_f16.c +3rdparty/softfloat/source/extF80_to_f32.c +3rdparty/softfloat/source/extF80_to_f64.c +3rdparty/softfloat/source/extF80_to_i32.c +3rdparty/softfloat/source/extF80_to_i32_r_minMag.c +3rdparty/softfloat/source/extF80_to_i64.c +3rdparty/softfloat/source/extF80_to_i64_r_minMag.c +3rdparty/softfloat/source/extF80_to_ui32.c +3rdparty/softfloat/source/extF80_to_ui32_r_minMag.c +3rdparty/softfloat/source/extF80_to_ui64.c +3rdparty/softfloat/source/extF80_to_ui64_r_minMag.c +3rdparty/softfloat/source/f128M_add.c +3rdparty/softfloat/source/f128M_div.c +3rdparty/softfloat/source/f128M_eq.c +3rdparty/softfloat/source/f128M_eq_signaling.c +3rdparty/softfloat/source/f128M_le.c +3rdparty/softfloat/source/f128M_le_quiet.c +3rdparty/softfloat/source/f128M_lt.c +3rdparty/softfloat/source/f128M_lt_quiet.c +3rdparty/softfloat/source/f128M_mul.c +3rdparty/softfloat/source/f128M_mulAdd.c +3rdparty/softfloat/source/f128M_rem.c +3rdparty/softfloat/source/f128M_roundToInt.c +3rdparty/softfloat/source/f128M_sqrt.c +3rdparty/softfloat/source/f128M_sub.c +3rdparty/softfloat/source/f128M_to_extF80M.c +3rdparty/softfloat/source/f128M_to_f16.c +3rdparty/softfloat/source/f128M_to_f32.c +3rdparty/softfloat/source/f128M_to_f64.c +3rdparty/softfloat/source/f128M_to_i32.c +3rdparty/softfloat/source/f128M_to_i32_r_minMag.c +3rdparty/softfloat/source/f128M_to_i64.c +3rdparty/softfloat/source/f128M_to_i64_r_minMag.c +3rdparty/softfloat/source/f128M_to_ui32.c +3rdparty/softfloat/source/f128M_to_ui32_r_minMag.c +3rdparty/softfloat/source/f128M_to_ui64.c +3rdparty/softfloat/source/f128M_to_ui64_r_minMag.c +3rdparty/softfloat/source/f128_add.c +3rdparty/softfloat/source/f128_div.c +3rdparty/softfloat/source/f128_eq.c +3rdparty/softfloat/source/f128_eq_signaling.c +3rdparty/softfloat/source/f128_isSignalingNaN.c +3rdparty/softfloat/source/f128_le.c +3rdparty/softfloat/source/f128_le_quiet.c +3rdparty/softfloat/source/f128_lt.c +3rdparty/softfloat/source/f128_lt_quiet.c +3rdparty/softfloat/source/f128_mul.c +3rdparty/softfloat/source/f128_mulAdd.c +3rdparty/softfloat/source/f128_rem.c +3rdparty/softfloat/source/f128_roundToInt.c +3rdparty/softfloat/source/f128_sqrt.c +3rdparty/softfloat/source/f128_sub.c +3rdparty/softfloat/source/f128_to_extF80.c +3rdparty/softfloat/source/f128_to_f16.c +3rdparty/softfloat/source/f128_to_f32.c +3rdparty/softfloat/source/f128_to_f64.c +3rdparty/softfloat/source/f128_to_i32.c +3rdparty/softfloat/source/f128_to_i32_r_minMag.c +3rdparty/softfloat/source/f128_to_i64.c +3rdparty/softfloat/source/f128_to_i64_r_minMag.c +3rdparty/softfloat/source/f128_to_ui32.c +3rdparty/softfloat/source/f128_to_ui32_r_minMag.c +3rdparty/softfloat/source/f128_to_ui64.c +3rdparty/softfloat/source/f128_to_ui64_r_minMag.c +3rdparty/softfloat/source/f16_add.c +3rdparty/softfloat/source/f16_div.c +3rdparty/softfloat/source/f16_eq.c +3rdparty/softfloat/source/f16_eq_signaling.c +3rdparty/softfloat/source/f16_isSignalingNaN.c +3rdparty/softfloat/source/f16_le.c +3rdparty/softfloat/source/f16_le_quiet.c +3rdparty/softfloat/source/f16_lt.c +3rdparty/softfloat/source/f16_lt_quiet.c +3rdparty/softfloat/source/f16_mul.c +3rdparty/softfloat/source/f16_mulAdd.c +3rdparty/softfloat/source/f16_rem.c +3rdparty/softfloat/source/f16_roundToInt.c +3rdparty/softfloat/source/f16_sqrt.c +3rdparty/softfloat/source/f16_sub.c +3rdparty/softfloat/source/f16_to_extF80.c +3rdparty/softfloat/source/f16_to_extF80M.c +3rdparty/softfloat/source/f16_to_f128.c +3rdparty/softfloat/source/f16_to_f128M.c +3rdparty/softfloat/source/f16_to_f32.c +3rdparty/softfloat/source/f16_to_f64.c +3rdparty/softfloat/source/f16_to_i32.c +3rdparty/softfloat/source/f16_to_i32_r_minMag.c +3rdparty/softfloat/source/f16_to_i64.c +3rdparty/softfloat/source/f16_to_i64_r_minMag.c +3rdparty/softfloat/source/f16_to_ui32.c +3rdparty/softfloat/source/f16_to_ui32_r_minMag.c +3rdparty/softfloat/source/f16_to_ui64.c +3rdparty/softfloat/source/f16_to_ui64_r_minMag.c +3rdparty/softfloat/source/f32_add.c +3rdparty/softfloat/source/f32_div.c +3rdparty/softfloat/source/f32_eq.c +3rdparty/softfloat/source/f32_eq_signaling.c +3rdparty/softfloat/source/f32_isSignalingNaN.c +3rdparty/softfloat/source/f32_le.c +3rdparty/softfloat/source/f32_le_quiet.c +3rdparty/softfloat/source/f32_lt.c +3rdparty/softfloat/source/f32_lt_quiet.c +3rdparty/softfloat/source/f32_mul.c +3rdparty/softfloat/source/f32_mulAdd.c +3rdparty/softfloat/source/f32_rem.c +3rdparty/softfloat/source/f32_roundToInt.c +3rdparty/softfloat/source/f32_sqrt.c +3rdparty/softfloat/source/f32_sub.c +3rdparty/softfloat/source/f32_to_bf16.c +3rdparty/softfloat/source/f32_to_extF80.c +3rdparty/softfloat/source/f32_to_extF80M.c +3rdparty/softfloat/source/f32_to_f128.c +3rdparty/softfloat/source/f32_to_f128M.c +3rdparty/softfloat/source/f32_to_f16.c +3rdparty/softfloat/source/f32_to_f64.c +3rdparty/softfloat/source/f32_to_i32.c +3rdparty/softfloat/source/f32_to_i32_r_minMag.c +3rdparty/softfloat/source/f32_to_i64.c +3rdparty/softfloat/source/f32_to_i64_r_minMag.c +3rdparty/softfloat/source/f32_to_ui32.c +3rdparty/softfloat/source/f32_to_ui32_r_minMag.c +3rdparty/softfloat/source/f32_to_ui64.c +3rdparty/softfloat/source/f32_to_ui64_r_minMag.c +3rdparty/softfloat/source/f64_add.c +3rdparty/softfloat/source/f64_div.c +3rdparty/softfloat/source/f64_eq.c +3rdparty/softfloat/source/f64_eq_signaling.c +3rdparty/softfloat/source/f64_isSignalingNaN.c +3rdparty/softfloat/source/f64_le.c +3rdparty/softfloat/source/f64_le_quiet.c +3rdparty/softfloat/source/f64_lt.c +3rdparty/softfloat/source/f64_lt_quiet.c +3rdparty/softfloat/source/f64_mul.c +3rdparty/softfloat/source/f64_mulAdd.c +3rdparty/softfloat/source/f64_rem.c +3rdparty/softfloat/source/f64_roundToInt.c +3rdparty/softfloat/source/f64_sqrt.c +3rdparty/softfloat/source/f64_sub.c +3rdparty/softfloat/source/f64_to_bf16.c +3rdparty/softfloat/source/f64_to_extF80.c +3rdparty/softfloat/source/f64_to_extF80M.c +3rdparty/softfloat/source/f64_to_f128.c +3rdparty/softfloat/source/f64_to_f128M.c +3rdparty/softfloat/source/f64_to_f16.c +3rdparty/softfloat/source/f64_to_f32.c +3rdparty/softfloat/source/f64_to_i32.c +3rdparty/softfloat/source/f64_to_i32_r_minMag.c +3rdparty/softfloat/source/f64_to_i64.c +3rdparty/softfloat/source/f64_to_i64_r_minMag.c +3rdparty/softfloat/source/f64_to_ui32.c +3rdparty/softfloat/source/f64_to_ui32_r_minMag.c +3rdparty/softfloat/source/f64_to_ui64.c +3rdparty/softfloat/source/f64_to_ui64_r_minMag.c +3rdparty/softfloat/source/i32_to_extF80.c +3rdparty/softfloat/source/i32_to_extF80M.c +3rdparty/softfloat/source/i32_to_f128.c +3rdparty/softfloat/source/i32_to_f128M.c +3rdparty/softfloat/source/i32_to_f16.c +3rdparty/softfloat/source/i32_to_f32.c +3rdparty/softfloat/source/i32_to_f64.c +3rdparty/softfloat/source/i64_to_extF80.c +3rdparty/softfloat/source/i64_to_extF80M.c +3rdparty/softfloat/source/i64_to_f128.c +3rdparty/softfloat/source/i64_to_f128M.c +3rdparty/softfloat/source/i64_to_f16.c +3rdparty/softfloat/source/i64_to_f32.c +3rdparty/softfloat/source/i64_to_f64.c +3rdparty/softfloat/source/s_add128.c +3rdparty/softfloat/source/s_add256M.c +3rdparty/softfloat/source/s_addMagsExtF80.c +3rdparty/softfloat/source/s_addMagsF128.c +3rdparty/softfloat/source/s_addMagsF16.c +3rdparty/softfloat/source/s_addMagsF32.c +3rdparty/softfloat/source/s_addMagsF64.c +3rdparty/softfloat/source/s_approxRecip32_1.c +3rdparty/softfloat/source/s_approxRecipSqrt32_1.c +3rdparty/softfloat/source/s_approxRecipSqrt_1Ks.c +3rdparty/softfloat/source/s_approxRecip_1Ks.c +3rdparty/softfloat/source/s_countLeadingZeros16.c +3rdparty/softfloat/source/s_countLeadingZeros32.c +3rdparty/softfloat/source/s_countLeadingZeros64.c +3rdparty/softfloat/source/s_countLeadingZeros8.c +3rdparty/softfloat/source/s_eq128.c +3rdparty/softfloat/source/s_le128.c +3rdparty/softfloat/source/s_lt128.c +3rdparty/softfloat/source/s_mul128By32.c +3rdparty/softfloat/source/s_mul128To256M.c +3rdparty/softfloat/source/s_mul64ByShifted32To128.c +3rdparty/softfloat/source/s_mul64To128.c +3rdparty/softfloat/source/s_mulAddF128.c +3rdparty/softfloat/source/s_mulAddF16.c +3rdparty/softfloat/source/s_mulAddF32.c +3rdparty/softfloat/source/s_mulAddF64.c +3rdparty/softfloat/source/s_normRoundPackToExtF80.c +3rdparty/softfloat/source/s_normRoundPackToF128.c +3rdparty/softfloat/source/s_normRoundPackToF16.c +3rdparty/softfloat/source/s_normRoundPackToF32.c +3rdparty/softfloat/source/s_normRoundPackToF64.c +3rdparty/softfloat/source/s_normSubnormalBF16Sig.c +3rdparty/softfloat/source/s_normSubnormalExtF80Sig.c +3rdparty/softfloat/source/s_normSubnormalF128Sig.c +3rdparty/softfloat/source/s_normSubnormalF16Sig.c +3rdparty/softfloat/source/s_normSubnormalF32Sig.c +3rdparty/softfloat/source/s_normSubnormalF64Sig.c +3rdparty/softfloat/source/s_roundPackToBF16.c +3rdparty/softfloat/source/s_roundPackToExtF80.c +3rdparty/softfloat/source/s_roundPackToF128.c +3rdparty/softfloat/source/s_roundPackToF16.c +3rdparty/softfloat/source/s_roundPackToF32.c +3rdparty/softfloat/source/s_roundPackToF64.c +3rdparty/softfloat/source/s_roundToI32.c +3rdparty/softfloat/source/s_roundToI64.c +3rdparty/softfloat/source/s_roundToUI32.c +3rdparty/softfloat/source/s_roundToUI64.c +3rdparty/softfloat/source/s_shiftRightJam128.c +3rdparty/softfloat/source/s_shiftRightJam128Extra.c +3rdparty/softfloat/source/s_shiftRightJam256M.c +3rdparty/softfloat/source/s_shiftRightJam32.c +3rdparty/softfloat/source/s_shiftRightJam64.c +3rdparty/softfloat/source/s_shiftRightJam64Extra.c +3rdparty/softfloat/source/s_shortShiftLeft128.c +3rdparty/softfloat/source/s_shortShiftRight128.c +3rdparty/softfloat/source/s_shortShiftRightJam128.c +3rdparty/softfloat/source/s_shortShiftRightJam128Extra.c +3rdparty/softfloat/source/s_shortShiftRightJam64.c +3rdparty/softfloat/source/s_shortShiftRightJam64Extra.c +3rdparty/softfloat/source/s_sub128.c +3rdparty/softfloat/source/s_sub256M.c +3rdparty/softfloat/source/s_subMagsExtF80.c +3rdparty/softfloat/source/s_subMagsF128.c +3rdparty/softfloat/source/s_subMagsF16.c +3rdparty/softfloat/source/s_subMagsF32.c +3rdparty/softfloat/source/s_subMagsF64.c +3rdparty/softfloat/source/softfloat_state.c +3rdparty/softfloat/source/ui32_to_extF80.c +3rdparty/softfloat/source/ui32_to_extF80M.c +3rdparty/softfloat/source/ui32_to_f128.c +3rdparty/softfloat/source/ui32_to_f128M.c +3rdparty/softfloat/source/ui32_to_f16.c +3rdparty/softfloat/source/ui32_to_f32.c +3rdparty/softfloat/source/ui32_to_f64.c +3rdparty/softfloat/source/ui64_to_extF80.c +3rdparty/softfloat/source/ui64_to_extF80M.c +3rdparty/softfloat/source/ui64_to_f128.c +3rdparty/softfloat/source/ui64_to_f128M.c +3rdparty/softfloat/source/ui64_to_f16.c +3rdparty/softfloat/source/ui64_to_f32.c +3rdparty/softfloat/source/ui64_to_f64.c diff --git a/ps5lift/runtime/src/Cpuid.cpp b/ps5lift/runtime/src/Cpuid.cpp new file mode 100644 index 000000000000..d31980416004 --- /dev/null +++ b/ps5lift/runtime/src/Cpuid.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include + +namespace { + +constexpr std::uint32_t Bit(const unsigned index) { + return std::uint32_t{1} << index; +} + +constexpr std::uint32_t MaximumLeaf = 0xd; +constexpr std::uint32_t MaximumExtendedLeaf = 0x80000001; +constexpr std::uint32_t VendorEbx = 0x68747541; +constexpr std::uint32_t VendorEdx = 0x69746e65; +constexpr std::uint32_t VendorEcx = 0x444d4163; +constexpr std::uint32_t Signature = 0x00840f70; +constexpr std::uint32_t Leaf1Ebx = 16u << 16 | 8u << 8; +constexpr std::uint32_t Leaf1Ecx = Bit(0) | Bit(1) | Bit(9) | Bit(12) | Bit(13) | Bit(19) | Bit(20) | Bit(22) | Bit(23) | Bit(25) | Bit(26) | Bit(27) | Bit(28) | Bit(29) | Bit(30); +constexpr std::uint32_t Leaf1Edx = 0x178bfbff; +constexpr std::uint32_t Leaf7Ebx = Bit(3) | Bit(5) | Bit(8) | Bit(18) | Bit(19) | Bit(23) | Bit(24) | Bit(29); +constexpr std::uint32_t Leaf7Ecx = Bit(22); +constexpr std::uint64_t EnabledState = 0x7; +constexpr std::uint32_t XsaveSize = 0x340; +constexpr std::uint32_t XsaveFeatures = Bit(0) | Bit(1) | Bit(2) | Bit(3); +constexpr std::uint32_t AvxStateSize = 0x100; +constexpr std::uint32_t AvxStateOffset = 0x240; +constexpr std::uint32_t Leaf80000001Ecx = Bit(0) | Bit(5) | Bit(6) | Bit(7) | Bit(8); +constexpr std::uint32_t Leaf80000001Edx = 0x2fd3fbff; + +struct Answer { + std::uint32_t Eax; + std::uint32_t Ebx; + std::uint32_t Ecx; + std::uint32_t Edx; +}; + +Answer Identify(const std::uint64_t address, const std::uint32_t leaf, const std::uint32_t subleaf) { + switch (leaf) { + case 0x0: return {MaximumLeaf, VendorEbx, VendorEcx, VendorEdx}; + case 0x1: return {Signature, Leaf1Ebx, Leaf1Ecx, Leaf1Edx}; + case 0x2: case 0x3: case 0x4: case 0x8: case 0x9: case 0xa: case 0xb: case 0xc: return {}; + case 0x7: return subleaf == 0 ? Answer{0, Leaf7Ebx, Leaf7Ecx, 0} : Answer{}; + case 0xd: + if (subleaf == 0) return {static_cast(EnabledState), XsaveSize, XsaveSize, 0}; + if (subleaf == 1) return {XsaveFeatures, XsaveSize, 0, 0}; + if (subleaf == 2) return {AvxStateSize, AvxStateOffset, 0, 0}; + return {}; + case 0x80000000: return {MaximumExtendedLeaf, VendorEbx, VendorEcx, VendorEdx}; + case 0x80000001: return {Signature, 0, Leaf80000001Ecx, Leaf80000001Edx}; + default: + if (leaf <= MaximumLeaf || (leaf >= 0x80000000 && leaf <= MaximumExtendedLeaf)) ps5lift_fail("cpuid leaf 0x%" PRIx32 " at 0x%" PRIx64 " has no PS5 value", leaf, address); + return {}; + } +} + +} + +extern "C" void ps5lift_cpuid(Ps5LiftState* state, const std::uint64_t address) { + const auto answer = Identify(address, static_cast(state->Gpr[PS5LIFT_RAX]), static_cast(state->Gpr[PS5LIFT_RCX])); + state->Gpr[PS5LIFT_RAX] = answer.Eax; + state->Gpr[PS5LIFT_RBX] = answer.Ebx; + state->Gpr[PS5LIFT_RCX] = answer.Ecx; + state->Gpr[PS5LIFT_RDX] = answer.Edx; +} + +extern "C" std::uint64_t ps5lift_xgetbv(const std::uint64_t address, const std::uint32_t index) { + if (index > 1) ps5lift_fail("xgetbv register 0x%" PRIx32 " at 0x%" PRIx64 " has no PS5 value", index, address); + return EnabledState; +} diff --git a/ps5lift/runtime/src/GuestCpu.cpp b/ps5lift/runtime/src/GuestCpu.cpp new file mode 100644 index 000000000000..098d6633bea0 --- /dev/null +++ b/ps5lift/runtime/src/GuestCpu.cpp @@ -0,0 +1,271 @@ +#include "Runtime.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +extern "C" std::uint32_t GuestCpuMxcsr_nid_no_patch(void) { + return ps5lift_read_mxcsr(ps5lift_current_state()); +} + +extern "C" void GuestCpuSetMxcsr_nid_no_patch(const std::uint32_t value) { + auto* state = ps5lift_current_state(); + state->Mxcsr = value; + ps5lift_apply_mxcsr(state); +} + +extern "C" std::uint16_t GuestCpuX87Control_nid_no_patch(void) { + return ps5lift_current_state()->X87Control; +} + +extern "C" void GuestCpuSetX87Control_nid_no_patch(const std::uint16_t value) { + ps5lift_current_state()->X87Control = value; +} + +extern "C" std::uint16_t GuestCpuX87Status_nid_no_patch(void) { + return ps5lift_current_state()->X87Status; +} + +extern "C" void GuestCpuSetX87Status_nid_no_patch(const std::uint16_t value) { + ps5lift_current_state()->X87Status = value; +} + +extern "C" void GuestCpuX87Wait_nid_no_patch(void) { + Ps5Lift::RequireNoPendingX87Exceptions(*ps5lift_current_state()); +} + +extern "C" std::uint32_t GuestCpuMxcsrMask_nid_no_patch(void) { + return PS5LIFT_MXCSR_MASK; +} + +#ifdef PS5LIFT_HOST_LONG_DOUBLE_HOLDS_EXTENDED +extern "C" void GuestCpuStoreExtended_nid_no_patch(const long double* host, void* guest) { + ps5lift_extended_from_host(ps5lift_current_state(), host, guest); +} +#endif + +extern "C" void GuestCpuExtendedAdd_nid_no_patch(const void* left, const void* right, void* result) { + ps5lift_extended_operation(ps5lift_current_state(), PS5LIFT_X87_ADD, left, right, result); +} + +extern "C" void GuestCpuExtendedSubtract_nid_no_patch(const void* left, const void* right, void* result) { + ps5lift_extended_operation(ps5lift_current_state(), PS5LIFT_X87_SUBTRACT, left, right, result); +} + +extern "C" void GuestCpuExtendedMultiply_nid_no_patch(const void* left, const void* right, void* result) { + ps5lift_extended_operation(ps5lift_current_state(), PS5LIFT_X87_MULTIPLY, left, right, result); +} + +extern "C" void GuestCpuExtendedDivide_nid_no_patch(const void* left, const void* right, void* result) { + ps5lift_extended_operation(ps5lift_current_state(), PS5LIFT_X87_DIVIDE, left, right, result); +} + +extern "C" void GuestCpuExtendedSquareRoot_nid_no_patch(const void* value, void* result) { + ps5lift_extended_operation(ps5lift_current_state(), PS5LIFT_X87_SQUARE_ROOT, value, value, result); +} + +extern "C" void GuestCpuExtendedRoundToIntegral_nid_no_patch(const void* value, void* result) { + ps5lift_extended_operation(ps5lift_current_state(), PS5LIFT_X87_ROUND, value, value, result); +} + +extern "C" double GuestCpuExtendedToDouble_nid_no_patch(const void* value) { + double result; + ps5lift_extended_store(ps5lift_current_state(), PS5LIFT_X87_FLOAT64, value, &result); + return result; +} + +extern "C" float GuestCpuExtendedToFloat_nid_no_patch(const void* value) { + float result; + ps5lift_extended_store(ps5lift_current_state(), PS5LIFT_X87_FLOAT32, value, &result); + return result; +} + +extern "C" std::int64_t GuestCpuExtendedToInt64_nid_no_patch(const void* value) { + std::int64_t result; + ps5lift_extended_store(ps5lift_current_state(), PS5LIFT_X87_INTEGER64 | PS5LIFT_X87_TRUNCATE, value, &result); + return result; +} + +namespace { + +constexpr int DwarfRegisters[16] = {PS5LIFT_RAX, PS5LIFT_RDX, PS5LIFT_RCX, PS5LIFT_RBX, PS5LIFT_RSI, PS5LIFT_RDI, PS5LIFT_RBP, PS5LIFT_RSP, + PS5LIFT_R8, PS5LIFT_R9, PS5LIFT_R10, PS5LIFT_R11, PS5LIFT_R12, PS5LIFT_R13, PS5LIFT_R14, PS5LIFT_R15}; +constexpr int DwarfRsp = 7; +constexpr int DwarfRip = 16; + +bool SameName(const std::string_view left, const std::string_view right) { + const auto fold = [](const unsigned char value) { return value >= 'A' && value <= 'Z' ? value + ('a' - 'A') : value; }; + return std::ranges::equal(left, right, [&](const unsigned char a, const unsigned char b) { return fold(a) == fold(b); }); +} + +struct LoadedImage { + std::uint64_t Address; + const char* Name; + const void* ProgramHeaders; + std::uint16_t ProgramHeaderCount; + unsigned long long Loads; + unsigned long long Unloads; + std::size_t TlsModule; + void* TlsData; +}; + +} + +extern "C" void GuestCpuUnwindStart_nid_no_patch(std::uint64_t* registers) { + const auto* state = ps5lift_current_state(); + for (int i = 0; i < 16; ++i) registers[i] = state->Gpr[DwarfRegisters[i]]; + std::memcpy(®isters[DwarfRip], reinterpret_cast(registers[DwarfRsp]), sizeof(registers[DwarfRip])); + registers[DwarfRsp] += 8; +} + +extern "C" int GuestCpuIterateImages_nid_no_patch(int (*callback)(LoadedImage*, std::size_t, void*), void* data) { + const auto& program = Ps5Lift::Program(); + const auto* state = Ps5Lift::BoundState(); + for (std::uint64_t index = 0; index < program.ImageCount; ++index) { + const auto& image = program.Images[index]; + LoadedImage loaded{image.LoadBias, image.Name, reinterpret_cast(image.ProgramHeaders), static_cast(image.ProgramHeaderCount), program.ImageCount, 0, image.TlsModule, + image.TlsModule == 0 || state == nullptr ? nullptr : reinterpret_cast(state->FsBase - image.TlsOffset)}; + if (const int result = callback(&loaded, sizeof(loaded), data); result != 0) return result; + } + return 0; +} + +extern "C" void GuestCpuMakingWritable_nid_no_patch(const std::uint64_t address, const std::uint64_t length) { + constexpr std::uint32_t Write = 2; + const auto& program = Ps5Lift::Program(); + for (std::uint64_t index = 0; index < program.ImageCount; ++index) { + const auto& image = program.Images[index]; + for (std::uint64_t i = 0; i < image.SegmentCount; ++i) { + const auto& segment = image.Segments[i]; + if ((segment.Protection & Write) != 0 || address >= segment.GuestAddress + segment.MemorySize || segment.GuestAddress >= address + length) continue; + ps5lift_fail("the guest makes 0x%" PRIx64 "-0x%" PRIx64 " writable, inside the read-only segment at 0x%" PRIx64 " of %s, whose loads were lifted unordered", address, address + length, + segment.GuestAddress, image.Name); + } + } +} + +extern "C" const char* GuestCpuImportName_nid_no_patch(const std::uint64_t address) { + const auto& program = Ps5Lift::Program(); + for (std::uint64_t index = 0; index < program.ImageCount; ++index) { + const auto& image = program.Images[index]; + for (std::uint64_t i = 0; i < image.ImportCount; ++i) { + if (image.Imports[i].GuestAddress == address) return image.Imports[i].Name; + } + } + return nullptr; +} + +extern "C" [[noreturn]] void GuestCpuUnwindResume_nid_no_patch(const std::uint64_t* registers) { + auto* state = ps5lift_current_state(); + for (int i = 0; i < 16; ++i) state->Gpr[DwarfRegisters[i]] = registers[i]; + state->ResumeRip = registers[DwarfRip]; + state->ResumeRsp = registers[DwarfRsp]; + state->Unwinding = 1; + throw Ps5Lift::Bridge::GuestUnwind{}; +} + +extern "C" std::uint64_t GuestCpuReadTsc_nid_no_patch(void) { + return ps5lift_read_tsc(); +} + +namespace { + +std::uint64_t ImageSymbol(const Ps5LiftImage& image, const char* name) { + const auto* begin = image.Exports; + const auto* end = begin + image.ExportCount; + const auto* found = std::lower_bound(begin, end, name, [](const Ps5LiftExport& entry, const char* wanted) { return std::strcmp(entry.Name, wanted) < 0; }); + return found != end && std::strcmp(found->Name, name) == 0 ? found->Address : 0; +} + +} + +extern "C" std::uint64_t GuestCpuFindImage_nid_no_patch(const char* path) { + const auto& program = Ps5Lift::Program(); + const std::string_view wanted(path); + std::uint64_t found = 0; + std::size_t matched = 0; + for (std::uint64_t index = 1; index < program.ImageCount; ++index) { + const std::string_view name(program.Images[index].Name); + if (name.size() <= matched || wanted.size() < name.size() || !SameName(wanted.substr(wanted.size() - name.size()), name)) continue; + if (wanted.size() != name.size() && wanted[wanted.size() - name.size() - 1] != '/') continue; + found = index + 1; + matched = name.size(); + } + return found; +} + +extern "C" std::uint64_t GuestCpuImageSymbol_nid_no_patch(const std::uint64_t image, const char* name) { + const auto& program = Ps5Lift::Program(); + if (image > program.ImageCount) ps5lift_fail("GuestCpuImageSymbol: there is no image %" PRIu64, image); + if (image != 0) return ImageSymbol(program.Images[image - 1], name); + for (std::uint64_t index = 0; index < program.ImageCount; ++index) { + if (const auto address = ImageSymbol(program.Images[index], name)) return address; + } + return 0; +} + +extern "C" std::uint64_t GuestCpuCall_nid_no_patch(const std::uint64_t target, const std::uint64_t first, const std::uint64_t second, const std::uint64_t third) { + return Ps5Lift::Bridge::CallGuest(target, first, second, third); +} + +extern "C" void GuestCpuUseStack_nid_no_patch(const std::uint64_t stackTop) { + ps5lift_use_guest_stack(stackTop); +} + +extern "C" std::uint64_t GuestCpuHostStackSize_nid_no_patch(void) { + return ps5lift_host_stack_size(); +} + +extern "C" void GuestCpuCallInterrupted_nid_no_patch(const std::uint64_t target, const std::uint64_t first, const std::uint64_t second) { + auto* state = ps5lift_current_state(); + const Ps5LiftState saved = *state; + const auto mxcsr = ps5lift_read_mxcsr(state); + state->Gpr[PS5LIFT_RSP] = ps5lift_interrupt_stack(); + state->Unwinding = 0; + Ps5Lift::Bridge::CallGuest(target, first, second); + *state = saved; + state->Mxcsr = mxcsr; + ps5lift_apply_mxcsr(state); +} + +extern "C" void GuestCpuInterruptedContext_nid_no_patch(std::uint64_t* registers, std::uint64_t* flags) { + static constexpr unsigned FlagBits[PS5LIFT_FLAG_COUNT] = {0, 2, 4, 6, 7, 11, 10}; + const auto* state = ps5lift_current_state(); + for (int i = 0; i < 16; ++i) registers[i] = state->Gpr[DwarfRegisters[i]]; + registers[DwarfRip] = state->Rip; + std::uint64_t value = 0x202; + for (int i = 0; i < PS5LIFT_FLAG_COUNT; ++i) value |= static_cast(state->Flags[i] & 1) << FlagBits[i]; + *flags = value; +} + +namespace { + +constexpr int FiberRegisters[] = {PS5LIFT_RBX, PS5LIFT_RBP, PS5LIFT_R12, PS5LIFT_R13, PS5LIFT_R14, PS5LIFT_R15, PS5LIFT_RSP}; + +} + +extern "C" void GuestCpuSaveFiber_nid_no_patch(std::uint64_t* context) { + auto* state = ps5lift_current_state(); + for (int i = 0; i < 7; ++i) context[i] = state->Gpr[FiberRegisters[i]]; + context[7] = static_cast(ps5lift_read_mxcsr(state)) | static_cast(state->X87Control) << 32; +} + +extern "C" void GuestCpuRestoreFiber_nid_no_patch(const std::uint64_t* context) { + auto* state = ps5lift_current_state(); + for (int i = 0; i < 7; ++i) state->Gpr[FiberRegisters[i]] = context[i]; + state->Mxcsr = static_cast(context[7]); + state->X87Control = static_cast(context[7] >> 32); + state->Unwinding = 0; + ps5lift_apply_mxcsr(state); +} + +extern "C" void GuestCpuStartFiber_nid_no_patch(const std::uint64_t stackTop) { + auto* state = ps5lift_current_state(); + if (stackTop != 0) state->Gpr[PS5LIFT_RSP] = stackTop & ~std::uint64_t{15}; + state->Unwinding = 0; +} diff --git a/ps5lift/runtime/src/Jump.cpp b/ps5lift/runtime/src/Jump.cpp new file mode 100644 index 000000000000..afbf7256f2da --- /dev/null +++ b/ps5lift/runtime/src/Jump.cpp @@ -0,0 +1,77 @@ +#include + +#include +#include +#include +#include + +extern "C" { +[[gnu::weak]] void JumpBufferSaveMask_nid_no_patch(unsigned char* buffer); +[[gnu::weak]] void JumpBufferRestoreMask_nid_no_patch(const unsigned char* buffer); +} + +namespace { + +constexpr std::size_t MaskSavedOffset = 88; +constexpr int SavedRegisters[] = {PS5LIFT_RBX, PS5LIFT_RSP, PS5LIFT_RBP, PS5LIFT_R12, PS5LIFT_R13, PS5LIFT_R14, PS5LIFT_R15}; + +template +void Put(unsigned char* buffer, const std::size_t offset, const T value) { + std::memcpy(buffer + offset, &value, sizeof(value)); +} + +template +T Get(const unsigned char* buffer, const std::size_t offset) { + T value; + std::memcpy(&value, buffer + offset, sizeof(value)); + return value; +} + +void Save(Ps5LiftState* state, const bool mask) { + auto* buffer = reinterpret_cast(state->Gpr[PS5LIFT_RDI]); + const auto rsp = state->Gpr[PS5LIFT_RSP]; + Put(buffer, 0, Get(reinterpret_cast(rsp), 0)); + for (std::size_t i = 0; i < std::size(SavedRegisters); ++i) { + const auto reg = SavedRegisters[i]; + Put(buffer, 8 + 8 * i, reg == PS5LIFT_RSP ? rsp + 8 : state->Gpr[reg]); + } + Put(buffer, 64, ps5lift_read_mxcsr(state)); + Put(buffer, 68, state->X87Control); + if (mask && JumpBufferSaveMask_nid_no_patch != nullptr) JumpBufferSaveMask_nid_no_patch(buffer); + else Put(buffer, MaskSavedOffset, std::int32_t{0}); + state->Gpr[PS5LIFT_RAX] = 0; +} + +void Jump(Ps5LiftState* state, const bool mask) { + const auto* buffer = reinterpret_cast(state->Gpr[PS5LIFT_RDI]); + const auto value = static_cast(state->Gpr[PS5LIFT_RSI]); + if (mask && JumpBufferRestoreMask_nid_no_patch != nullptr) JumpBufferRestoreMask_nid_no_patch(buffer); + for (std::size_t i = 0; i < std::size(SavedRegisters); ++i) state->Gpr[SavedRegisters[i]] = Get(buffer, 8 + 8 * i); + state->Gpr[PS5LIFT_RAX] = value != 0 ? value : 1; + state->Mxcsr = Get(buffer, 64); + ps5lift_apply_mxcsr(state); + state->X87Control = Get(buffer, 68); + state->ResumeRip = Get(buffer, 0); + state->ResumeRsp = state->Gpr[PS5LIFT_RSP]; + state->Unwinding = 1; +} + +void SaveWithRequestedMask(Ps5LiftState* state) { + Save(state, static_cast(state->Gpr[PS5LIFT_RSI]) != 0); +} + +} + +extern "C" void ps5lift_import_setjmp(Ps5LiftState* state) { Save(state, true); } +extern "C" void ps5lift_import___setjmp(Ps5LiftState* state) { Save(state, false); } +extern "C" void ps5lift_import_sigsetjmp(Ps5LiftState* state) { SaveWithRequestedMask(state); } +extern "C" void ps5lift_import_longjmp(Ps5LiftState* state) { Jump(state, true); } +extern "C" void ps5lift_import___longjmp(Ps5LiftState* state) { Jump(state, false); } +extern "C" void ps5lift_import_siglongjmp(Ps5LiftState* state) { Jump(state, true); } + +extern "C" void ps5lift_import_gNQ1V2vfXDE(Ps5LiftState* state) { Save(state, true); } +extern "C" void ps5lift_import_sjpkrhugvVI(Ps5LiftState* state) { Save(state, false); } +extern "C" void ps5lift_import_aRo9AhFUXcM(Ps5LiftState* state) { SaveWithRequestedMask(state); } +extern "C" void ps5lift_import_lKEN2IebgJ0(Ps5LiftState* state) { Jump(state, true); } +extern "C" void ps5lift_import__pF_p9hhi6k9Q(Ps5LiftState* state) { Jump(state, false); } +extern "C" void ps5lift_import_pebqbE5ws8s(Ps5LiftState* state) { Jump(state, true); } diff --git a/ps5lift/runtime/src/Main.cpp b/ps5lift/runtime/src/Main.cpp new file mode 100644 index 000000000000..5f09de22c763 --- /dev/null +++ b/ps5lift/runtime/src/Main.cpp @@ -0,0 +1,9 @@ +#include + +extern "C" const Ps5LiftProgram ps5lift_program; + +int main() { + ps5lift_use_program(&ps5lift_program); + ps5lift_run_main(); + return 0; +} diff --git a/ps5lift/runtime/src/NativeCall.cpp b/ps5lift/runtime/src/NativeCall.cpp new file mode 100644 index 000000000000..7ed9ec892254 --- /dev/null +++ b/ps5lift/runtime/src/NativeCall.cpp @@ -0,0 +1,185 @@ +#include "Runtime.hpp" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace { + +struct NativeRegisters { + std::uint64_t Integer[8]; + unsigned char Vector[8][16]; +}; + +static_assert(sizeof(NativeRegisters) == 192); + +constexpr int GuestIntegerArguments = 6; +constexpr int GuestVectorArguments = 8; +constexpr int StackedIntegerArguments = 2; + +struct sigaction PreviousSegv; +struct sigaction PreviousBus; + +bool InsideGuestImage(const std::uint64_t address) { + const auto& program = Ps5Lift::Program(); + for (std::uint64_t image = 0; image < program.ImageCount; ++image) { + const auto& entry = program.Images[image]; + for (std::uint64_t segment = 0; segment < entry.SegmentCount; ++segment) { + const auto& mapped = entry.Segments[segment]; + if (address - mapped.GuestAddress < mapped.MemorySize) return true; + } + } + return false; +} + +} + +extern "C" void ps5lift_native_call(const std::uint64_t target, NativeRegisters* const registers) { + using namespace Ps5Lift::Bridge; + const GuestEntry entry; + auto* state = entry.State(); + for (int index = 0; index < GuestIntegerArguments; ++index) state->Gpr[IntegerRegisters[index]] = registers->Integer[index]; + for (int index = 0; index < GuestVectorArguments; ++index) { + std::memset(state->Vector[index], 0, sizeof(state->Vector[index])); + std::memcpy(state->Vector[index], registers->Vector[index], sizeof(registers->Vector[index])); + } + RunGuest(state, target, registers->Integer + GuestIntegerArguments, StackedIntegerArguments, GuestVectorArguments); + registers->Integer[0] = state->Gpr[PS5LIFT_RAX]; + registers->Integer[1] = state->Gpr[PS5LIFT_RDX]; + std::memcpy(registers->Vector[0], state->Vector[0], sizeof(registers->Vector[0])); + std::memcpy(registers->Vector[1], state->Vector[1], sizeof(registers->Vector[1])); +} + +extern "C" void ps5lift_native_call_trampoline(); + +#if defined(__aarch64__) +__asm__(".text\n" + ".p2align 2\n" + ".type ps5lift_native_call_trampoline, %function\n" + "ps5lift_native_call_trampoline:\n" + " .cfi_startproc\n" + " stp x29, x30, [sp, #-16]!\n" + " .cfi_def_cfa_offset 16\n" + " .cfi_offset x29, -16\n" + " .cfi_offset x30, -8\n" + " mov x29, sp\n" + " .cfi_def_cfa_register x29\n" + " sub sp, sp, #192\n" + " stp x0, x1, [sp, #0]\n" + " stp x2, x3, [sp, #16]\n" + " stp x4, x5, [sp, #32]\n" + " stp x6, x7, [sp, #48]\n" + " stp q0, q1, [sp, #64]\n" + " stp q2, q3, [sp, #96]\n" + " stp q4, q5, [sp, #128]\n" + " stp q6, q7, [sp, #160]\n" + " mov x0, x16\n" + " mov x1, sp\n" + " bl ps5lift_native_call\n" + " ldp x0, x1, [sp, #0]\n" + " ldp q0, q1, [sp, #64]\n" + " mov sp, x29\n" + " .cfi_def_cfa_register sp\n" + " ldp x29, x30, [sp], #16\n" + " .cfi_def_cfa_offset 0\n" + " .cfi_restore x29\n" + " .cfi_restore x30\n" + " ret\n" + " .cfi_endproc\n" + ".size ps5lift_native_call_trampoline, . - ps5lift_native_call_trampoline\n"); +#elif defined(__x86_64__) +__asm__(".text\n" + ".p2align 4\n" + ".type ps5lift_native_call_trampoline, @function\n" + "ps5lift_native_call_trampoline:\n" + " .cfi_startproc\n" + " push %rbp\n" + " .cfi_def_cfa_offset 16\n" + " .cfi_offset %rbp, -16\n" + " mov %rsp, %rbp\n" + " .cfi_def_cfa_register %rbp\n" + " sub $192, %rsp\n" + " mov %rdi, 0(%rsp)\n" + " mov %rsi, 8(%rsp)\n" + " mov %rdx, 16(%rsp)\n" + " mov %rcx, 24(%rsp)\n" + " mov %r8, 32(%rsp)\n" + " mov %r9, 40(%rsp)\n" + " mov 16(%rbp), %rax\n" + " mov %rax, 48(%rsp)\n" + " mov 24(%rbp), %rax\n" + " mov %rax, 56(%rsp)\n" + " movdqu %xmm0, 64(%rsp)\n" + " movdqu %xmm1, 80(%rsp)\n" + " movdqu %xmm2, 96(%rsp)\n" + " movdqu %xmm3, 112(%rsp)\n" + " movdqu %xmm4, 128(%rsp)\n" + " movdqu %xmm5, 144(%rsp)\n" + " movdqu %xmm6, 160(%rsp)\n" + " movdqu %xmm7, 176(%rsp)\n" + " mov %r11, %rdi\n" + " mov %rsp, %rsi\n" + " call ps5lift_native_call\n" + " mov 0(%rsp), %rax\n" + " mov 8(%rsp), %rdx\n" + " movdqu 64(%rsp), %xmm0\n" + " movdqu 80(%rsp), %xmm1\n" + " leave\n" + " .cfi_def_cfa %rsp, 8\n" + " ret\n" + " .cfi_endproc\n" + ".size ps5lift_native_call_trampoline, . - ps5lift_native_call_trampoline\n"); +#else +#error "native calls into guest code need a trampoline for this host" +#endif + +namespace { + +void Redirect(const int signal, siginfo_t* const info, void* const context) { + auto& machine = static_cast(context)->uc_mcontext; +#if defined(__aarch64__) + auto& pc = machine.pc; + auto& scratch = machine.regs[16]; +#elif defined(__x86_64__) + auto& pc = machine.gregs[REG_RIP]; + auto& scratch = machine.gregs[REG_R11]; +#endif + const auto address = static_cast(pc); + if (reinterpret_cast(info->si_addr) == address && InsideGuestImage(address)) { + if (Ps5Lift::LookupFunction(address) == nullptr) ps5lift_fail("native code called guest address 0x%" PRIx64 ", which is not a lifted function", address); + scratch = address; + pc = reinterpret_cast(&ps5lift_native_call_trampoline); + return; + } + const auto& previous = signal == SIGBUS ? PreviousBus : PreviousSegv; + if ((previous.sa_flags & SA_SIGINFO) != 0) return previous.sa_sigaction(signal, info, context); + if (previous.sa_handler == SIG_DFL || previous.sa_handler == SIG_IGN) { + struct sigaction fallback {}; + fallback.sa_handler = SIG_DFL; + sigemptyset(&fallback.sa_mask); + sigaction(signal, &fallback, nullptr); + return; + } + previous.sa_handler(signal); +} + +} + +namespace Ps5Lift { + +void InstallNativeCalls() { + struct sigaction action {}; + action.sa_sigaction = Redirect; + action.sa_flags = SA_SIGINFO; + sigemptyset(&action.sa_mask); + if (sigaction(SIGSEGV, &action, &PreviousSegv) != 0 || sigaction(SIGBUS, &action, &PreviousBus) != 0) ps5lift_fail("cannot install the handler for native calls into guest code: %s", std::strerror(errno)); +} + +} diff --git a/ps5lift/runtime/src/Runtime.cpp b/ps5lift/runtime/src/Runtime.cpp new file mode 100644 index 000000000000..277915e36681 --- /dev/null +++ b/ps5lift/runtime/src/Runtime.cpp @@ -0,0 +1,583 @@ +#include "Runtime.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__x86_64__) +#include +#endif + +extern "C" { +[[gnu::weak]] bool GuestArenaContains_nid_postfix(const void* pointer, std::size_t bytes); +[[gnu::weak]] void GuestArenaCommit_nid_postfix(void* pointer, std::size_t bytes, std::uint32_t protection, std::size_t granule); +} + +namespace { + +constexpr std::uint32_t ArenaReadWrite = 0x04; +constexpr std::uint64_t GuestStackSize = 8ull << 20; +constexpr std::size_t HostStackSize = 512ull << 20; +constexpr std::uint64_t TlsSize = 64ull << 10; +constexpr std::uint32_t ConsoleMxcsr = 0x9fc0; +constexpr std::uint16_t ConsoleX87Control = 0x037f; + +std::uint64_t PageSize() { + return static_cast(sysconf(_SC_PAGESIZE)); +} + +void BindDataImports(const Ps5LiftImage& image) { + for (std::uint64_t i = 0; i < image.DataImportCount; ++i) { + const auto& import = image.DataImports[i]; + const auto value = reinterpret_cast(*import.Address) + static_cast(import.Addend); + std::memcpy(reinterpret_cast(import.Slot), &value, sizeof(value)); + } +} + +void MapSegments(const Ps5LiftImage& image) { + if (image.SegmentCount == 0) ps5lift_fail("the image has no segments"); + const auto page = PageSize(); + auto begin = ~std::uint64_t{0}; + std::uint64_t end = 0; + for (std::uint64_t i = 0; i < image.SegmentCount; ++i) { + begin = std::min(begin, image.Segments[i].GuestAddress & ~(page - 1)); + end = std::max(end, (image.Segments[i].GuestAddress + image.Segments[i].MemorySize + page - 1) & ~(page - 1)); + } + auto* const start = reinterpret_cast(begin); + if (GuestArenaContains_nid_postfix != nullptr && GuestArenaContains_nid_postfix(start, end - begin)) { + GuestArenaCommit_nid_postfix(start, end - begin, ArenaReadWrite, end - begin); + for (std::uint64_t i = 0; i < image.SegmentCount; ++i) std::memcpy(reinterpret_cast(image.Segments[i].GuestAddress), image.Segments[i].Data, image.Segments[i].FileSize); + BindDataImports(image); + return; + } + auto* mapped = mmap(start, end - begin, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); + if (mapped != start) ps5lift_fail("cannot map the guest image at 0x%" PRIx64 "-0x%" PRIx64 ": %s", begin, end, std::strerror(errno)); + for (std::uint64_t i = 0; i < image.SegmentCount; ++i) std::memcpy(reinterpret_cast(image.Segments[i].GuestAddress), image.Segments[i].Data, image.Segments[i].FileSize); + BindDataImports(image); +} + +void* MapAnonymous(const std::uint64_t size) { + auto* mapped = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped == MAP_FAILED) ps5lift_fail("cannot allocate %" PRIu64 " bytes: %s", size, std::strerror(errno)); + return mapped; +} + +struct ThreadMemory { + std::uint8_t* Stack = nullptr; + std::uint8_t* Tls = nullptr; + std::uint8_t* InterruptStack = nullptr; +}; + +struct HostFloatingPoint { +#if defined(__aarch64__) + std::uint64_t Fpcr = 0; + std::uint64_t Fpsr = 0; +#elif defined(__x86_64__) + std::uint32_t Mxcsr = 0; +#endif +}; + +HostFloatingPoint SaveHostFloatingPoint() { + HostFloatingPoint saved; +#if defined(__aarch64__) + __asm__ volatile("mrs %0, fpcr" : "=r"(saved.Fpcr)); + __asm__ volatile("mrs %0, fpsr" : "=r"(saved.Fpsr)); +#elif defined(__x86_64__) + saved.Mxcsr = _mm_getcsr(); +#endif + return saved; +} + +void RestoreHostFloatingPoint(const HostFloatingPoint& saved) { +#if defined(__aarch64__) + __asm__ volatile("msr fpcr, %0" : : "r"(saved.Fpcr)); + __asm__ volatile("msr fpsr, %0" : : "r"(saved.Fpsr)); +#elif defined(__x86_64__) + _mm_setcsr(saved.Mxcsr); +#endif +} + +struct ForeignThread { + ThreadMemory Memory; + Ps5LiftState State{}; + std::uint64_t Entered = 0; + HostFloatingPoint Host; +}; + +thread_local Ps5LiftState* CurrentState = nullptr; +thread_local std::uint64_t InterruptStackTop = 0; +thread_local ForeignThread* Foreign = nullptr; + +bool GuestEnvironmentLive() { + return Foreign == nullptr || Foreign->Entered != 0; +} + +std::uint8_t* MapGuestStack() { + const auto guard = PageSize(); + auto* mapping = static_cast(MapAnonymous(guard + GuestStackSize)); + if (mprotect(mapping, guard, PROT_NONE) != 0) ps5lift_fail("cannot protect the guard page below a guest stack: %s", std::strerror(errno)); + return mapping + guard; +} + +void UnmapGuestStack(std::uint8_t* const stack) { + const auto guard = PageSize(); + munmap(stack - guard, guard + GuestStackSize); +} + +std::uint64_t StackTop(const std::uint8_t* const stack) { + return reinterpret_cast(stack + GuestStackSize); +} + +ThreadMemory MapThreadMemory(const bool ownStack) { + return {ownStack ? MapGuestStack() : nullptr, static_cast(MapAnonymous(TlsSize)), MapGuestStack()}; +} + +void UnmapThreadMemory(const ThreadMemory& memory) { + if (memory.Stack != nullptr) UnmapGuestStack(memory.Stack); + munmap(memory.Tls, TlsSize); + UnmapGuestStack(memory.InterruptStack); +} + +void BindThread(Ps5LiftState* const state, const ThreadMemory& memory) { + CurrentState = state; + InterruptStackTop = StackTop(memory.InterruptStack); +} + +void UnbindThread() { + CurrentState = nullptr; + InterruptStackTop = 0; +} + +void PrepareState(Ps5LiftState& state, std::uint8_t* const tls, const std::uint32_t mxcsr) { + const auto& program = Ps5Lift::Program(); + const auto threadPointer = reinterpret_cast(tls + TlsSize / 2); + std::memcpy(reinterpret_cast(threadPointer), &threadPointer, sizeof(threadPointer)); + for (std::uint64_t i = 0; i < program.ImageCount; ++i) { + const auto& image = program.Images[i]; + if (image.TlsMemorySize == 0) continue; + if (image.TlsOffset > TlsSize / 2) ps5lift_fail("the static TLS of image %llu does not fit", static_cast(i)); + std::memcpy(reinterpret_cast(threadPointer - image.TlsOffset), reinterpret_cast(image.TlsAddress), image.TlsFileSize); + } + state.FsBase = threadPointer; + state.Mxcsr = mxcsr & ~0x3fu; + state.X87Control = ConsoleX87Control; +} + +void Initialize(const std::uint64_t argc, const std::uint64_t argv, const std::uint64_t environment) { + const auto& program = Ps5Lift::Program(); + for (std::uint64_t i = 0; i < program.InitializationCount; ++i) { + const auto& image = program.Images[program.Initialization[i]]; + if (image.Init != 0) Ps5Lift::Bridge::CallGuest(image.Init, argc, argv, environment); + for (std::uint64_t index = 0; index < image.InitCount; ++index) { + std::uint64_t initializer = 0; + std::memcpy(&initializer, reinterpret_cast(image.InitArray + 8 * index), sizeof(initializer)); + if (initializer != 0 && initializer != ~std::uint64_t{0}) Ps5Lift::Bridge::CallGuest(initializer, argc, argv, environment); + } + } +} + +void* RunMain(void* argument) { + const auto entry = *static_cast(argument); + Ps5LiftState state{}; + const auto memory = MapThreadMemory(true); + PrepareState(state, memory.Tls, ConsoleMxcsr); + const auto top = StackTop(memory.Stack); + BindThread(&state, memory); + ps5lift_apply_mxcsr(&state); + const auto function = Ps5Lift::LookupFunction(entry); + if (function == nullptr) ps5lift_fail("the program entry 0x%" PRIx64 " was not lifted", entry); + static constexpr char ProgramName[] = "eboot.bin"; + const auto name = top - sizeof(ProgramName); + std::memcpy(reinterpret_cast(name), ProgramName, sizeof(ProgramName)); + const std::uint64_t entryStack[] = {1, name, 0, 0, 0, 0}; + const auto rsp = (name - sizeof(entryStack)) & ~std::uint64_t{15}; + std::memcpy(reinterpret_cast(rsp), entryStack, sizeof(entryStack)); + state.Gpr[PS5LIFT_RSP] = rsp; + Initialize(entryStack[0], rsp + 8, rsp + 24); + function(&state); + if (state.Unwinding) ps5lift_fail("nothing on the main thread's guest stack resumes at 0x%" PRIx64, state.ResumeRip); + std::fflush(stdout); + std::_Exit(static_cast(state.Gpr[PS5LIFT_RAX] & 0xff)); +} + +void StartMain(std::uint64_t& entry) { + pthread_attr_t attributes; + pthread_attr_init(&attributes); + pthread_attr_setstacksize(&attributes, HostStackSize); + pthread_t handle{}; + const auto error = pthread_create(&handle, &attributes, RunMain, &entry); + pthread_attr_destroy(&attributes); + if (error != 0) ps5lift_fail("cannot start the main guest thread: %s", std::strerror(error)); + pthread_join(handle, nullptr); +} + +} + +namespace { + +constexpr std::uint32_t MxcsrExceptions = 0x3f; + +#if defined(__aarch64__) +constexpr std::uint64_t FpsrBits[6] = {1u << 0, 1u << 7, 1u << 1, 1u << 2, 1u << 3, 1u << 4}; +#endif + +std::uint32_t HostExceptions() { +#if defined(__aarch64__) + std::uint64_t fpsr = 0; + __asm__ volatile("mrs %0, fpsr" : "=r"(fpsr)); + std::uint32_t flags = 0; + for (unsigned i = 0; i < 6; ++i) { + if (fpsr & FpsrBits[i]) flags |= 1u << i; + } + return flags; +#elif defined(__x86_64__) + return _mm_getcsr() & MxcsrExceptions; +#else + return 0; +#endif +} + +void SetHostExceptions(const std::uint32_t flags) { +#if defined(__aarch64__) + std::uint64_t fpsr = 0; + __asm__ volatile("mrs %0, fpsr" : "=r"(fpsr)); + for (unsigned i = 0; i < 6; ++i) { + if (flags & (1u << i)) fpsr |= FpsrBits[i]; + else fpsr &= ~FpsrBits[i]; + } + __asm__ volatile("msr fpsr, %0" : : "r"(fpsr)); +#elif defined(__x86_64__) + _mm_setcsr((_mm_getcsr() & ~MxcsrExceptions) | (flags & MxcsrExceptions)); +#endif +} + +} + +extern "C" void ps5lift_apply_mxcsr(Ps5LiftState* state) { + if (!GuestEnvironmentLive()) return; + const auto mxcsr = state->Mxcsr; +#if defined(__aarch64__) + constexpr std::uint32_t MxcsrDenormalsAreZero = 1u << 6; + constexpr std::uint32_t MxcsrFlushToZero = 1u << 15; + constexpr std::uint64_t FlushToZero = 1u << 24; + constexpr std::uint64_t RoundingShift = 22; + static constexpr std::uint64_t Rounding[4] = {0, 2, 1, 3}; + std::uint64_t fpcr = 0; + __asm__ volatile("mrs %0, fpcr" : "=r"(fpcr)); + fpcr &= ~(FlushToZero | (std::uint64_t{3} << RoundingShift)); + fpcr |= Rounding[(mxcsr >> 13) & 3] << RoundingShift; + if (mxcsr & (MxcsrDenormalsAreZero | MxcsrFlushToZero)) fpcr |= FlushToZero; + __asm__ volatile("msr fpcr, %0" : : "r"(fpcr)); +#elif defined(__x86_64__) + _mm_setcsr((mxcsr & ~MxcsrExceptions) | 0x1f80u); +#endif + SetHostExceptions(mxcsr & MxcsrExceptions); +} + +extern "C" std::uint64_t ps5lift_read_tsc(void) { +#if defined(__aarch64__) + std::uint64_t ticks; + asm volatile("isb\n\tmrs %0, cntvct_el0" : "=r"(ticks)); + return ticks; +#else + return __rdtsc(); +#endif +} + +extern "C" void ps5lift_flush_cache_line(const std::uint64_t address) { +#if defined(__aarch64__) + asm volatile("dc civac, %0" : : "r"(address) : "memory"); +#else + _mm_clflush(reinterpret_cast(address)); +#endif +} + +extern "C" void ps5lift_write_back_cache_line(const std::uint64_t address) { +#if defined(__aarch64__) + asm volatile("dc cvac, %0" : : "r"(address) : "memory"); +#else + _mm_clflush(reinterpret_cast(address)); +#endif +} + +extern "C" std::uint64_t ps5lift_random(void) { + std::uint64_t value = 0; + if (getrandom(&value, sizeof(value), 0) != static_cast(sizeof(value))) ps5lift_fail("rdrand: the host gave no random bytes: %s", std::strerror(errno)); + return value; +} + +extern "C" std::uint32_t ps5lift_processor_id(void) { + const int processor = sched_getcpu(); + if (processor < 0) ps5lift_fail("rdpid: the host cannot tell which processor runs this thread: %s", std::strerror(errno)); + return static_cast(processor); +} + +extern "C" std::uint32_t ps5lift_read_mxcsr(Ps5LiftState* state) { + if (!GuestEnvironmentLive()) return state->Mxcsr; + return (state->Mxcsr & ~MxcsrExceptions) | HostExceptions(); +} + +extern "C" Ps5LiftFunction ps5lift_resolve(const std::uint64_t target) { + const auto function = Ps5Lift::LookupFunction(target); + if (function == nullptr) ps5lift_fail("indirect branch to 0x%" PRIx64 ", which was not lifted", target); + return function; +} + +extern "C" void ps5lift_dispatch(Ps5LiftState* state, const std::uint64_t target) { + ps5lift_resolve(target)(state); +} + +extern "C" void ps5lift_fail(const char* format, ...) { + va_list arguments; + va_start(arguments, format); + std::fputs("ps5lift: ", stderr); + std::vfprintf(stderr, format, arguments); + std::fputc('\n', stderr); + va_end(arguments); + std::fflush(stdout); + std::_Exit(70); +} + +extern "C" void ps5lift_import_____tls__get__addr(Ps5LiftState* state) { + const auto& program = Ps5Lift::Program(); + std::uint64_t index[2]; + std::memcpy(index, reinterpret_cast(state->Gpr[PS5LIFT_RDI]), sizeof(index)); + if (index[0] == 0 || index[0] > program.ImageCount || program.Images[index[0] - 1].TlsMemorySize == 0) + ps5lift_fail("__tls_get_addr of module %" PRIu64 ", which has no TLS", index[0]); + state->Gpr[PS5LIFT_RAX] = state->FsBase - program.Images[index[0] - 1].TlsOffset + index[1]; +} + +extern "C" void ps5lift_unsupported(Ps5LiftState*, const std::uint64_t address, const char* mnemonic) { + ps5lift_fail("reached unsupported instruction %s at 0x%" PRIx64, mnemonic, address); +} + +extern "C" void ps5lift_unknown_resume(Ps5LiftState*, const std::uint64_t function, const std::uint64_t resume) { + ps5lift_fail("a longjmp or exception resumes at 0x%" PRIx64 " in the frame of 0x%" PRIx64 ", which has no resume point there", resume, function); +} + +extern "C" void ps5lift_interrupt(Ps5LiftState* state, const std::uint64_t address, const std::uint32_t vector) { + std::uint32_t code = 0; + std::memcpy(&code, reinterpret_cast(state->FsBase + 0x28), sizeof(code)); + ps5lift_fail("guest interrupt 0x%x at 0x%" PRIx64 ", with 0x%08x at %%fs:0x28", vector, address, code); +} + +extern "C" void ps5lift_trap(Ps5LiftState*, const std::uint64_t address) { + ps5lift_fail("guest trap at 0x%" PRIx64, address); +} + +extern "C" void ps5lift_left_jump_table(Ps5LiftState*, const std::uint64_t address) { + ps5lift_fail("the jump at 0x%" PRIx64 " went outside its jump table, which discovery found closed", address); +} + +extern "C" void ps5lift_divide_error(Ps5LiftState*, const std::uint64_t address) { + ps5lift_fail("divide error at 0x%" PRIx64 ": the divisor is zero or the quotient does not fit", address); +} + +extern "C" void ps5lift_string_op(Ps5LiftState* state, const std::uint32_t operation, const std::uint32_t width, const std::uint32_t repeat, const std::uint64_t sourceBase) { + auto& rcx = state->Gpr[PS5LIFT_RCX]; + auto& rsi = state->Gpr[PS5LIFT_RSI]; + auto& rdi = state->Gpr[PS5LIFT_RDI]; + const bool forward = state->Flags[PS5LIFT_DF] == 0; + const std::int64_t step = forward ? static_cast(width) : -static_cast(width); + const std::uint64_t count = repeat ? rcx : 1; + const std::uint64_t bytes = count * width; + auto* destination = reinterpret_cast(rdi); + switch (operation) { + case PS5LIFT_STRING_MOVS: { + const auto* source = reinterpret_cast(rsi + sourceBase); + if (forward && (destination <= source || destination >= source + bytes)) { + std::memmove(destination, source, bytes); + } else { + for (std::uint64_t i = 0; i < count; ++i) std::memmove(destination + static_cast(i) * step, source + static_cast(i) * step, width); + } + rsi += static_cast(static_cast(count) * step); + break; + } + case PS5LIFT_STRING_STOS: + if (forward && width == 1) { + std::memset(destination, static_cast(state->Gpr[PS5LIFT_RAX] & 0xff), bytes); + } else { + for (std::uint64_t i = 0; i < count; ++i) std::memcpy(destination + static_cast(i) * step, &state->Gpr[PS5LIFT_RAX], width); + } + break; + default: + ps5lift_fail("unknown string operation %u", operation); + } + rdi += static_cast(static_cast(count) * step); + if (repeat) rcx = 0; +} + +namespace { + +void ReleaseForeignThread(void* pointer) { + auto* foreign = static_cast(pointer); + Foreign = nullptr; + UnbindThread(); + UnmapThreadMemory(foreign->Memory); + delete foreign; +} + +pthread_key_t ForeignThreadKey() { + static const pthread_key_t key = [] { + pthread_key_t created; + if (const int error = pthread_key_create(&created, ReleaseForeignThread); error != 0) ps5lift_fail("cannot create the key that releases native threads' guest state: %s", std::strerror(error)); + return created; + }(); + return key; +} + +Ps5LiftState* StartForeignThread(const ThreadMemory& memory, const std::uint64_t stackTop) { + auto* foreign = new ForeignThread; + foreign->Memory = memory; + if (const int error = pthread_setspecific(ForeignThreadKey(), foreign); error != 0) ps5lift_fail("cannot keep a native thread's guest state: %s", std::strerror(error)); + PrepareState(foreign->State, memory.Tls, ConsoleMxcsr); + foreign->State.Gpr[PS5LIFT_RSP] = stackTop & ~std::uint64_t{15}; + Foreign = foreign; + BindThread(&foreign->State, memory); + return CurrentState; +} + +} + +Ps5LiftState* Ps5Lift::BoundState() { + return CurrentState; +} + +extern "C" Ps5LiftState* ps5lift_current_state(void) { + if (CurrentState != nullptr) return CurrentState; + const auto memory = MapThreadMemory(true); + return StartForeignThread(memory, StackTop(memory.Stack)); +} + +extern "C" void ps5lift_use_guest_stack(const std::uint64_t stackTop) { + if (CurrentState != nullptr) ps5lift_fail("a thread that already runs guest code cannot move to another guest stack"); + StartForeignThread(MapThreadMemory(false), stackTop); +} + +extern "C" std::uint64_t ps5lift_interrupt_stack(void) { + ps5lift_current_state(); + return InterruptStackTop; +} + +extern "C" std::uint64_t ps5lift_host_stack_size(void) { + return HostStackSize; +} + +extern "C" Ps5LiftState* ps5lift_enter_guest(void) { + auto* state = ps5lift_current_state(); + if (Foreign != nullptr && Foreign->Entered++ == 0) { + Foreign->Host = SaveHostFloatingPoint(); + ps5lift_apply_mxcsr(state); + } + return state; +} + +extern "C" void ps5lift_leave_guest(void) { + if (Foreign == nullptr) return; + if (Foreign->Entered == 1) { + Foreign->State.Mxcsr = ps5lift_read_mxcsr(&Foreign->State); + RestoreHostFloatingPoint(Foreign->Host); + } + --Foreign->Entered; +} + +namespace { + +void RequireHostFeatures() { +#if defined(__aarch64__) + constexpr unsigned long required = HWCAP_AES | HWCAP_PMULL | HWCAP_CRC32; + if ((getauxval(AT_HWCAP) & required) != required) ps5lift_fail("the host CPU lacks AES, PMULL or CRC32, which lifted AES-NI, PCLMULQDQ and crc32 use"); +#else + if (!__builtin_cpu_supports("aes") || !__builtin_cpu_supports("pclmul") || !__builtin_cpu_supports("sse4.2")) ps5lift_fail("the host CPU lacks AES-NI, PCLMULQDQ or SSE4.2, which lifted code uses"); +#endif +} + +} + +namespace { + +const Ps5LiftProgram* UsedProgram = nullptr; + +class FunctionIndex { +public: + void Build(const Ps5LiftFunctionEntry* functions, const std::uint64_t count) { + const auto size = std::bit_ceil(std::max(2, count * 2)); + _slots.assign(size, Ps5LiftFunctionEntry{}); + _mask = size - 1; + _shift = 64 - std::countr_zero(size); + for (std::uint64_t i = 0; i < count; ++i) { + const auto& function = functions[i]; + if (function.GuestAddress == 0) ps5lift_fail("the lifted program has a function at guest address 0"); + auto slot = Home(function.GuestAddress); + for (; _slots[slot].GuestAddress != 0; slot = (slot + 1) & _mask) { + if (_slots[slot].GuestAddress == function.GuestAddress) ps5lift_fail("the lifted program lists the function at 0x%" PRIx64 " twice", function.GuestAddress); + } + _slots[slot] = function; + } + } + + Ps5LiftFunction Find(const std::uint64_t address) const { + for (auto slot = Home(address);; slot = (slot + 1) & _mask) { + const auto& entry = _slots[slot]; + if (entry.GuestAddress == address) return entry.Host; + if (entry.GuestAddress == 0) return nullptr; + } + } + +private: + std::uint64_t Home(const std::uint64_t address) const { + return (address * 0x9e3779b97f4a7c15ull) >> _shift; + } + + std::vector _slots; + std::uint64_t _mask = 0; + int _shift = 0; +}; + +FunctionIndex FunctionsByAddress; + +} + +extern "C" void ps5lift_use_program(const Ps5LiftProgram* program) { + if (UsedProgram == program) return; + if (UsedProgram != nullptr) ps5lift_fail("the executable handed the runtime a second lifted program"); + UsedProgram = program; + FunctionsByAddress.Build(program->Functions, program->FunctionCount); +} + +const Ps5LiftProgram& Ps5Lift::Program() { + if (UsedProgram == nullptr) ps5lift_fail("the executable has not handed the runtime its lifted program yet"); + return *UsedProgram; +} + +Ps5LiftFunction Ps5Lift::LookupFunction(const std::uint64_t address) { + Ps5Lift::Program(); + return FunctionsByAddress.Find(address); +} + +extern "C" void ps5lift_run_main(void) { + const auto& program = Ps5Lift::Program(); + RequireHostFeatures(); + for (std::uint64_t i = 0; i < program.ImageCount; ++i) { + MapSegments(program.Images[i]); + } + Ps5Lift::InstallNativeCalls(); + auto entry = program.Entry; + StartMain(entry); +} diff --git a/ps5lift/runtime/src/Runtime.hpp b/ps5lift/runtime/src/Runtime.hpp new file mode 100644 index 000000000000..216cea976873 --- /dev/null +++ b/ps5lift/runtime/src/Runtime.hpp @@ -0,0 +1,22 @@ +#ifndef PS5LIFT_RUNTIME_HPP +#define PS5LIFT_RUNTIME_HPP + +#include + +#include + +namespace Ps5Lift { + +const Ps5LiftProgram& Program(); + +Ps5LiftFunction LookupFunction(std::uint64_t address); + +Ps5LiftState* BoundState(); + +void InstallNativeCalls(); + +void RequireNoPendingX87Exceptions(const Ps5LiftState& state); + +} + +#endif diff --git a/ps5lift/runtime/src/StringCompare.cpp b/ps5lift/runtime/src/StringCompare.cpp new file mode 100644 index 000000000000..e128bca93e97 --- /dev/null +++ b/ps5lift/runtime/src/StringCompare.cpp @@ -0,0 +1,99 @@ +#include + +#include +#include + +namespace { + +constexpr std::uint32_t Words = 1u << 0; +constexpr std::uint32_t Signed = 1u << 1; +constexpr std::uint32_t EqualAny = 0; +constexpr std::uint32_t Ranges = 1; +constexpr std::uint32_t EqualEach = 2; +constexpr std::uint32_t Negative = 1; +constexpr std::uint32_t MaskedNegative = 3; + +struct Operand { + std::uint64_t Low; + std::uint64_t High; +}; + +std::int32_t Element(const Operand& operand, const unsigned index, const std::uint32_t control) { + const unsigned bits = (control & Words) ? 16 : 8; + const unsigned shift = index * bits; + const auto half = shift < 64 ? operand.Low : operand.High; + const auto raw = static_cast((half >> (shift % 64)) & ((1u << bits) - 1)); + if (!(control & Signed)) return static_cast(raw); + return (control & Words) ? static_cast(raw) : static_cast(raw); +} + +unsigned ImplicitLength(const Operand& operand, const unsigned count, const std::uint32_t control) { + for (unsigned index = 0; index < count; ++index) { + if (Element(operand, index, control) == 0) return index; + } + return count; +} + +unsigned ExplicitLength(const std::int64_t value, const unsigned count) { + const auto magnitude = value < 0 ? 0 - static_cast(value) : static_cast(value); + return static_cast(std::min(magnitude, count)); +} + +std::uint32_t Aggregate(const Operand& first, const Operand& second, const unsigned firstLength, const unsigned secondLength, const unsigned count, const std::uint32_t control) { + const auto a = [&](const unsigned index) { return Element(first, index, control); }; + const auto b = [&](const unsigned index) { return Element(second, index, control); }; + std::uint32_t result = 0; + switch ((control >> 2) & 3) { + case EqualAny: + for (unsigned j = 0; j < secondLength; ++j) { + for (unsigned i = 0; i < firstLength; ++i) { + if (a(i) == b(j)) { + result |= 1u << j; + break; + } + } + } + return result; + case Ranges: + for (unsigned j = 0; j < secondLength; ++j) { + for (unsigned i = 0; i + 1 < firstLength; i += 2) { + if (a(i) <= b(j) && b(j) <= a(i + 1)) { + result |= 1u << j; + break; + } + } + } + return result; + case EqualEach: + for (unsigned i = 0; i < count; ++i) { + const bool firstValid = i < firstLength; + const bool secondValid = i < secondLength; + if (firstValid == secondValid && (!firstValid || a(i) == b(i))) result |= 1u << i; + } + return result; + default: + for (unsigned j = 0; j < count; ++j) { + bool match = true; + for (unsigned k = 0; k < firstLength && j + k < count && match; ++k) match = j + k < secondLength && a(k) == b(j + k); + if (match) result |= 1u << j; + } + return result; + } +} + +} + +extern "C" uint32_t ps5lift_string_compare(const uint64_t firstLow, const uint64_t firstHigh, const uint64_t secondLow, const uint64_t secondHigh, const int64_t firstLength, + const int64_t secondLength, const uint32_t control) { + const Operand first{firstLow, firstHigh}; + const Operand second{secondLow, secondHigh}; + const unsigned count = (control & Words) ? 8 : 16; + const bool implicit = (control & PS5LIFT_STRING_IMPLICIT) != 0; + const auto firstValid = implicit ? ImplicitLength(first, count, control) : ExplicitLength(firstLength, count); + const auto secondValid = implicit ? ImplicitLength(second, count, control) : ExplicitLength(secondLength, count); + auto result = Aggregate(first, second, firstValid, secondValid, count, control); + const auto polarity = (control >> 4) & 3; + if (polarity == Negative) result ^= (1u << count) - 1; + if (polarity == MaskedNegative) result ^= (1u << secondValid) - 1; + return result | (secondValid < count ? PS5LIFT_STRING_SHORT_SECOND : 0) | (firstValid < count ? PS5LIFT_STRING_SHORT_FIRST : 0); +} diff --git a/ps5lift/runtime/src/X87.cpp b/ps5lift/runtime/src/X87.cpp new file mode 100644 index 000000000000..3c59a78e329f --- /dev/null +++ b/ps5lift/runtime/src/X87.cpp @@ -0,0 +1,801 @@ +#include "Runtime.hpp" + +#include + +extern "C" { +#include +} + +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint16_t StatusInvalid = 1u << 0; +constexpr std::uint16_t StatusDenormal = 1u << 1; +constexpr std::uint16_t StatusZeroDivide = 1u << 2; +constexpr std::uint16_t StatusOverflow = 1u << 3; +constexpr std::uint16_t StatusUnderflow = 1u << 4; +constexpr std::uint16_t StatusPrecision = 1u << 5; +constexpr std::uint16_t StatusStackFault = 1u << 6; +constexpr std::uint16_t StatusSummary = 1u << 7; +constexpr std::uint16_t StatusC0 = 1u << 8; +constexpr std::uint16_t StatusC1 = 1u << 9; +constexpr std::uint16_t StatusC2 = 1u << 10; +constexpr std::uint16_t StatusC3 = 1u << 14; +constexpr std::uint16_t StatusBusy = 1u << 15; +constexpr std::uint16_t StatusConditions = StatusC0 | StatusC1 | StatusC2 | StatusC3; +constexpr unsigned TopShift = 11; +constexpr std::size_t FxStateBytes = 416; +constexpr std::uint16_t InitialControl = 0x037f; + + +extFloat80_t ReadExtended(const void* source) { + extFloat80_t value; + std::memcpy(&value.signif, source, 8); + std::memcpy(&value.signExp, static_cast(source) + 8, 2); + return value; +} + +void WriteExtended(void* destination, const extFloat80_t value) { + std::memcpy(destination, &value.signif, 8); + std::memcpy(static_cast(destination) + 8, &value.signExp, 2); +} + +std::uint_fast8_t SoftFloatRounding(const std::uint16_t control) { + static constexpr std::uint_fast8_t Rounding[4] = {softfloat_round_near_even, softfloat_round_min, softfloat_round_max, softfloat_round_minMag}; + return Rounding[(control >> 10) & 3]; +} + +extFloat80_t Indefinite() { + extFloat80_t value; + value.signExp = 0xffff; + value.signif = 0xc000000000000000ull; + return value; +} + +extFloat80_t Zero(const bool negative) { + extFloat80_t value; + value.signExp = negative ? 0x8000 : 0; + value.signif = 0; + return value; +} + +bool IsNan(const extFloat80_t value) { + return (value.signExp & 0x7fff) == 0x7fff && (value.signif & 0x7fffffffffffffffull) != 0; +} + +bool IsDenormal(const extFloat80_t value) { + return (value.signExp & 0x7fff) == 0 && value.signif != 0; +} + +bool IsNegative(const extFloat80_t value) { + return (value.signExp & 0x8000) != 0; +} + +bool IsZero(const extFloat80_t value) { + return (value.signExp & 0x7fff) == 0 && value.signif == 0; +} + +bool IsInfinity(const extFloat80_t value) { + return (value.signExp & 0x7fff) == 0x7fff && value.signif == 0x8000000000000000ull; +} + +extFloat80_t Infinity(const bool negative) { + extFloat80_t value; + value.signExp = negative ? 0xffff : 0x7fff; + value.signif = 0x8000000000000000ull; + return value; +} + +class Fpu { +public: + explicit Fpu(Ps5LiftState* state) : _state(*state) { + const auto control = _state.X87Control; + static constexpr std::uint_fast8_t Precision[4] = {32, 80, 64, 80}; + softfloat_roundingMode = SoftFloatRounding(control); + extF80_roundingPrecision = Precision[(control >> 8) & 3]; + softfloat_detectTininess = softfloat_tininess_afterRounding; + softfloat_exceptionFlags = 0; + } + + ~Fpu() { + std::uint16_t raised = 0; + const auto flags = softfloat_exceptionFlags; + if (flags & softfloat_flag_invalid) raised |= StatusInvalid; + if (flags & softfloat_flag_infinite) raised |= StatusZeroDivide; + if (flags & softfloat_flag_overflow) raised |= StatusOverflow; + if (flags & softfloat_flag_underflow) raised |= StatusUnderflow; + if (flags & softfloat_flag_inexact) raised |= StatusPrecision; + raised |= _extra; + auto status = static_cast(_state.X87Status | raised); + if ((status & ~_state.X87Control & 0x3f) != 0) status |= StatusSummary | StatusBusy; + _state.X87Status = status; + } + + unsigned Top() const { return (_state.X87Status >> TopShift) & 7; } + + void SetTop(const unsigned top) { + _state.X87Status = static_cast((_state.X87Status & ~(7u << TopShift)) | ((top & 7) << TopShift)); + } + + unsigned Physical(const unsigned index) const { return (Top() + index) & 7; } + + bool Valid(const unsigned index) const { return (_state.X87Valid >> Physical(index)) & 1; } + + extFloat80_t Get(const unsigned index) { + if (!Valid(index)) { + StackFault(false); + return Indefinite(); + } + return Contents(index); + } + + extFloat80_t Contents(const unsigned index) const { + return ReadExtended(_state.X87Registers[Physical(index)]); + } + + void Set(const unsigned index, const extFloat80_t value) { + const auto physical = Physical(index); + WriteExtended(_state.X87Registers[physical], value); + _state.X87Valid = static_cast(_state.X87Valid | (1u << physical)); + } + + void Push(const extFloat80_t value) { + SetTop(Top() - 1); + if (Valid(0)) { + StackFault(true); + Set(0, Indefinite()); + return; + } + Set(0, value); + } + + void Pop() { + _state.X87Valid = static_cast(_state.X87Valid & ~(1u << Physical(0))); + SetTop(Top() + 1); + } + + void Free(const unsigned index) { + _state.X87Valid = static_cast(_state.X87Valid & ~(1u << Physical(index))); + } + + void SetConditions(const std::uint16_t conditions) { + _state.X87Status = static_cast((_state.X87Status & ~StatusConditions) | conditions); + } + + void Raise(const std::uint16_t flags) { _extra |= flags; } + + void ClearRoundedUp() { + _state.X87Status = static_cast(_state.X87Status & ~StatusC1); + } + + void SetRoundedUp() { + _state.X87Status = static_cast(_state.X87Status | StatusC1); + } + + std::uint16_t Conditions() const { + return static_cast(_state.X87Status & StatusConditions); + } + + void Operands(const extFloat80_t left, const extFloat80_t right) { + if (IsNan(left) || IsNan(right)) return; + if (IsDenormal(left) || IsDenormal(right)) _extra |= StatusDenormal; + } + +private: + Ps5LiftState& _state; + std::uint16_t _extra = 0; + + void StackFault(const bool overflow) { + _extra |= StatusInvalid | StatusStackFault; + _state.X87Status = static_cast((_state.X87Status & ~StatusC1) | (overflow ? StatusC1 : 0)); + } +}; + +class FullPrecision { +public: + FullPrecision() : _saved(extF80_roundingPrecision) { extF80_roundingPrecision = 80; } + ~FullPrecision() { extF80_roundingPrecision = _saved; } + +private: + std::uint_fast8_t _saved; +}; + +bool SameBits(const extFloat80_t left, const extFloat80_t right) { + return left.signif == right.signif && left.signExp == right.signExp; +} + +bool SameBits(const float32_t left, const float32_t right) { + return left.v == right.v; +} + +bool SameBits(const float64_t left, const float64_t right) { + return left.v == right.v; +} + +template +auto WithRoundUp(Fpu& fpu, TCompute compute) -> decltype(compute()) { + const auto saved = softfloat_exceptionFlags; + softfloat_exceptionFlags = 0; + const auto result = compute(); + const auto raised = softfloat_exceptionFlags; + if (raised & softfloat_flag_inexact) { + const auto rounding = softfloat_roundingMode; + softfloat_roundingMode = softfloat_round_minMag; + const auto truncated = compute(); + softfloat_roundingMode = rounding; + if (!SameBits(result, truncated)) fpu.SetRoundedUp(); + } + softfloat_exceptionFlags = static_cast(saved | raised); + return result; +} + +extFloat80_t LoadMemory(Fpu& fpu, const std::uint32_t format, const std::uint64_t address) { + const void* source = reinterpret_cast(address); + switch (format & 7) { + case PS5LIFT_X87_FLOAT32: { + float32_t value; + std::memcpy(&value, source, 4); + if ((value.v & 0x7f800000u) == 0 && (value.v & 0x7fffffu) != 0) fpu.Raise(StatusDenormal); + return f32_to_extF80(value); + } + case PS5LIFT_X87_FLOAT64: { + float64_t value; + std::memcpy(&value, source, 8); + if ((value.v & 0x7ff0000000000000ull) == 0 && (value.v & 0xfffffffffffffull) != 0) fpu.Raise(StatusDenormal); + return f64_to_extF80(value); + } + case PS5LIFT_X87_FLOAT80: + return ReadExtended(source); + case PS5LIFT_X87_INTEGER16: { + std::int16_t value; + std::memcpy(&value, source, 2); + return i32_to_extF80(value); + } + case PS5LIFT_X87_INTEGER32: { + std::int32_t value; + std::memcpy(&value, source, 4); + return i32_to_extF80(value); + } + case PS5LIFT_X87_INTEGER64: { + std::int64_t value; + std::memcpy(&value, source, 8); + return i64_to_extF80(value); + } + default: + ps5lift_fail("unknown x87 memory format 0x%x", format); + } +} + +template +void StoreInteger(Fpu& fpu, const extFloat80_t value, const bool truncate, void* destination) { + const auto rounding = truncate ? static_cast(softfloat_round_minMag) : softfloat_roundingMode; + T result = static_cast(std::uint64_t{1} << (sizeof(T) * 8 - 1)); + if (IsNan(value)) { + fpu.Raise(StatusInvalid); + } else { + const auto saved = softfloat_exceptionFlags; + softfloat_exceptionFlags = 0; + const auto wide = extF80_to_i64(value, rounding, true); + const auto raised = softfloat_exceptionFlags; + const bool fits = (raised & softfloat_flag_invalid) == 0 && wide >= INT64_C(-1) << (sizeof(T) * 8 - 1) && (sizeof(T) == 8 || wide < INT64_C(1) << (sizeof(T) * 8 - 1)); + if (fits) { + result = static_cast(wide); + if ((raised & softfloat_flag_inexact) != 0 && !truncate && extF80_to_i64(value, softfloat_round_minMag, false) != wide) fpu.SetRoundedUp(); + } + softfloat_exceptionFlags = static_cast(saved | (fits ? raised : softfloat_flag_invalid)); + } + std::memcpy(destination, &result, sizeof(T)); +} + +void StoreMemory(Fpu& fpu, const std::uint32_t format, const std::uint64_t address, const extFloat80_t value) { + void* destination = reinterpret_cast(address); + const bool truncate = (format & PS5LIFT_X87_TRUNCATE) != 0; + switch (format & 7) { + case PS5LIFT_X87_FLOAT32: { + const auto result = WithRoundUp(fpu, [&] { return extF80_to_f32(value); }); + std::memcpy(destination, &result, 4); + return; + } + case PS5LIFT_X87_FLOAT64: { + const auto result = WithRoundUp(fpu, [&] { return extF80_to_f64(value); }); + std::memcpy(destination, &result, 8); + return; + } + case PS5LIFT_X87_FLOAT80: + WriteExtended(destination, value); + return; + case PS5LIFT_X87_INTEGER16: StoreInteger(fpu, value, truncate, destination); return; + case PS5LIFT_X87_INTEGER32: StoreInteger(fpu, value, truncate, destination); return; + case PS5LIFT_X87_INTEGER64: StoreInteger(fpu, value, truncate, destination); return; + default: ps5lift_fail("unknown x87 memory format 0x%x", format); + } +} + +extFloat80_t Arithmetic(const std::uint32_t operation, const extFloat80_t left, const extFloat80_t right) { + switch (operation) { + case PS5LIFT_X87_ADD: return extF80_add(left, right); + case PS5LIFT_X87_SUBTRACT: return extF80_sub(left, right); + case PS5LIFT_X87_SUBTRACT_REVERSE: return extF80_sub(right, left); + case PS5LIFT_X87_MULTIPLY: return extF80_mul(left, right); + case PS5LIFT_X87_DIVIDE: return extF80_div(left, right); + case PS5LIFT_X87_DIVIDE_REVERSE: return extF80_div(right, left); + default: ps5lift_fail("unknown x87 arithmetic operation %u", operation); + } +} + +extFloat80_t Compute(Fpu& fpu, const std::uint32_t operation, const extFloat80_t left, const extFloat80_t right) { + fpu.Operands(left, right); + switch (operation) { + case PS5LIFT_X87_SQUARE_ROOT: return WithRoundUp(fpu, [&] { return extF80_sqrt(left); }); + case PS5LIFT_X87_ROUND: return WithRoundUp(fpu, [&] { return extF80_roundToInt(left, softfloat_roundingMode, true); }); + default: return WithRoundUp(fpu, [&] { return Arithmetic(operation, left, right); }); + } +} + +std::uint32_t Compare(const extFloat80_t left, const extFloat80_t right, const bool unordered) { + const bool nan = IsNan(left) || IsNan(right); + if (nan) { + if (!unordered || extF80_isSignalingNaN(left) || extF80_isSignalingNaN(right)) softfloat_exceptionFlags |= softfloat_flag_invalid; + return PS5LIFT_X87_UNORDERED; + } + if (extF80_eq(left, right)) return PS5LIFT_X87_EQUAL; + return extF80_lt_quiet(left, right) ? PS5LIFT_X87_LESS : PS5LIFT_X87_GREATER; +} + +std::uint16_t CompareConditions(const std::uint32_t result) { + switch (result) { + case PS5LIFT_X87_GREATER: return 0; + case PS5LIFT_X87_LESS: return StatusC0; + case PS5LIFT_X87_EQUAL: return StatusC3; + case PS5LIFT_X87_UNORDERED: return StatusC0 | StatusC2 | StatusC3; + default: ps5lift_fail("unknown x87 comparison result %u", result); + } +} + +extFloat80_t Constant(const std::uint32_t index) { + static constexpr std::uint64_t Constants[5][2] = { + {0x4000a934f0979a37ull, 0x15fc9257edfe9b60ull}, + {0x3fff71547652b82full, 0xe1777d0ffda0d23aull}, + {0x4000921fb54442d1ull, 0x8469898cc51701b8ull}, + {0x3ffd34413509f79full, 0xef311f12b35816f9ull}, + {0x3ffe62e42fefa39eull, 0xf35793c7673007e6ull}, + }; + if (index == PS5LIFT_X87_CONSTANT_ONE) return i32_to_extF80(1); + if (index == PS5LIFT_X87_CONSTANT_ZERO) return Zero(false); + if (index > PS5LIFT_X87_CONSTANT_ZERO) ps5lift_fail("unknown x87 constant 0x%x", index); + float128_t value; + value.v[0] = Constants[index - PS5LIFT_X87_CONSTANT_LOG2_10][1]; + value.v[1] = Constants[index - PS5LIFT_X87_CONSTANT_LOG2_10][0]; + const auto savedPrecision = extF80_roundingPrecision; + extF80_roundingPrecision = 80; + const auto result = f128_to_extF80(value); + extF80_roundingPrecision = savedPrecision; + softfloat_exceptionFlags &= static_cast(~softfloat_flag_inexact); + return result; +} + +struct Significand { + std::uint64_t Bits; + int Exponent; +}; + +Significand Normalize(const extFloat80_t value) { + Significand result{value.signif, (value.signExp & 0x7fff) == 0 ? 1 : value.signExp & 0x7fff}; + while (result.Bits != 0 && (result.Bits >> 63) == 0) { + result.Bits <<= 1; + --result.Exponent; + } + return result; +} + +unsigned TruncatedQuotientBits(const extFloat80_t dividend, const extFloat80_t divisor) { + const auto x = Normalize(dividend); + const auto y = Normalize(divisor); + if (x.Bits == 0 || y.Bits == 0) return 0; + const int difference = x.Exponent - y.Exponent; + if (difference < 0) return 0; + unsigned __int128 remainder = x.Bits; + unsigned bits = 0; + for (int step = 0; step <= difference; ++step) { + const bool digit = remainder >= y.Bits; + if (digit) remainder -= y.Bits; + bits = ((bits << 1) | (digit ? 1u : 0u)) & 7u; + remainder <<= 1; + } + return bits; +} + +std::uint16_t Examine(Fpu& fpu) { + const auto value = fpu.Contents(0); + const auto sign = IsNegative(value) ? StatusC1 : 0; + if (!fpu.Valid(0)) return static_cast(StatusC3 | StatusC0 | sign); + const auto exponent = value.signExp & 0x7fff; + const bool integerBit = (value.signif >> 63) != 0; + if (exponent == 0x7fff) { + if (!integerBit) return sign; + return static_cast(((value.signif << 1) != 0 ? StatusC0 : StatusC2 | StatusC0) | sign); + } + if (exponent == 0) { + if (value.signif == 0) return static_cast(StatusC3 | sign); + return static_cast(StatusC3 | StatusC2 | sign); + } + if (!integerBit) return sign; + return static_cast(StatusC2 | sign); +} + +std::uint16_t TagWord(const Ps5LiftState& state) { + std::uint16_t tags = 0; + for (unsigned physical = 0; physical < 8; ++physical) { + unsigned tag = 3; + if ((state.X87Valid >> physical) & 1) { + const auto value = ReadExtended(state.X87Registers[physical]); + const auto exponent = value.signExp & 0x7fff; + if (exponent == 0 && value.signif == 0) tag = 1; + else if (exponent == 0x7fff || exponent == 0 || (value.signif >> 63) == 0) tag = 2; + else tag = 0; + } + tags = static_cast(tags | (tag << (2 * physical))); + } + return tags; +} + +} + +void Ps5Lift::RequireNoPendingX87Exceptions(const Ps5LiftState& state) { + const auto pending = state.X87Status & ~state.X87Control & 0x3fu; + if (pending != 0) ps5lift_fail("fwait with unmasked x87 exceptions 0x%x pending; the console raises SIGFPE", pending); +} + +extern "C" std::uint32_t ps5lift_x87(Ps5LiftState* state, const std::uint32_t operation, const std::uint32_t format, const std::uint64_t address, const std::uint32_t index) { + switch (operation) { + case PS5LIFT_X87_INITIALIZE: + state->X87Control = InitialControl; + state->X87Status = 0; + state->X87Valid = 0; + return 0; + case PS5LIFT_X87_CLEAR_EXCEPTIONS: + state->X87Status = static_cast(state->X87Status & 0x7f00); + return 0; + case PS5LIFT_X87_WAIT: + Ps5Lift::RequireNoPendingX87Exceptions(*state); + return 0; + case PS5LIFT_X87_STORE_STATUS: + return state->X87Status; + case PS5LIFT_X87_STORE_CONTROL: + std::memcpy(reinterpret_cast(address), &state->X87Control, 2); + return 0; + case PS5LIFT_X87_LOAD_CONTROL: + std::memcpy(&state->X87Control, reinterpret_cast(address), 2); + return 0; + case PS5LIFT_X87_STORE_ENVIRONMENT: { + const std::uint32_t environment[7] = {0xffff0000u | state->X87Control, 0xffff0000u | state->X87Status, 0xffff0000u | TagWord(*state), 0, 0, 0, 0xffff0000u}; + std::memcpy(reinterpret_cast(address), environment, sizeof(environment)); + state->X87Control = static_cast(state->X87Control | 0x3f); + return 0; + } + case PS5LIFT_X87_SAVE_STATE: { + if ((address & 15) != 0) ps5lift_fail("fxsave to 0x%llx, which is not 16-byte aligned; the console raises #GP", static_cast(address)); + std::uint8_t area[FxStateBytes] = {}; + std::memcpy(area, &state->X87Control, 2); + std::memcpy(area + 2, &state->X87Status, 2); + area[4] = state->X87Valid; + const std::uint32_t mxcsr[2] = {ps5lift_read_mxcsr(state), PS5LIFT_MXCSR_MASK}; + std::memcpy(area + 24, mxcsr, sizeof(mxcsr)); + const unsigned top = (state->X87Status >> TopShift) & 7; + for (unsigned index = 0; index < 8; ++index) std::memcpy(area + 32 + 16 * index, state->X87Registers[(top + index) & 7], 10); + for (unsigned reg = 0; reg < 16; ++reg) std::memcpy(area + 160 + 16 * reg, state->Vector[reg], 16); + std::memcpy(reinterpret_cast(address), area, sizeof(area)); + return 0; + } + case PS5LIFT_X87_RESTORE_STATE: { + if ((address & 15) != 0) ps5lift_fail("fxrstor from 0x%llx, which is not 16-byte aligned; the console raises #GP", static_cast(address)); + std::uint8_t area[FxStateBytes]; + std::memcpy(area, reinterpret_cast(address), sizeof(area)); + std::uint32_t mxcsr; + std::memcpy(&mxcsr, area + 24, 4); + if ((mxcsr & ~PS5LIFT_MXCSR_MASK) != 0) ps5lift_fail("fxrstor of MXCSR 0x%x, which sets reserved bits; the console raises #GP", mxcsr); + std::memcpy(&state->X87Control, area, 2); + std::memcpy(&state->X87Status, area + 2, 2); + state->X87Valid = area[4]; + const unsigned top = (state->X87Status >> TopShift) & 7; + for (unsigned index = 0; index < 8; ++index) { + auto* reg = state->X87Registers[(top + index) & 7]; + std::memcpy(reg, area + 32 + 16 * index, 10); + std::memset(reg + 10, 0, sizeof(state->X87Registers[0]) - 10); + } + for (unsigned reg = 0; reg < 16; ++reg) std::memcpy(state->Vector[reg], area + 160 + 16 * reg, 16); + state->Mxcsr = mxcsr; + ps5lift_apply_mxcsr(state); + return 0; + } + case PS5LIFT_X87_LOAD_ENVIRONMENT: { + std::uint32_t environment[7]; + std::memcpy(environment, reinterpret_cast(address), sizeof(environment)); + state->X87Control = static_cast(environment[0]); + state->X87Status = static_cast(environment[1]); + std::uint8_t valid = 0; + for (unsigned physical = 0; physical < 8; ++physical) { + if (((environment[2] >> (2 * physical)) & 3) != 3) valid = static_cast(valid | (1u << physical)); + } + state->X87Valid = valid; + return 0; + } + default: + break; + } + + Fpu fpu(state); + const bool memory = (format & 7) != PS5LIFT_X87_REGISTER; + switch (operation) { + case PS5LIFT_X87_LOAD: { + fpu.ClearRoundedUp(); + const auto value = memory ? LoadMemory(fpu, format, address) : fpu.Get(index); + fpu.Push(value); + return 0; + } + case PS5LIFT_X87_LOAD_CONSTANT: + fpu.ClearRoundedUp(); + fpu.Push(Constant(index)); + return 0; + case PS5LIFT_X87_STORE: { + fpu.ClearRoundedUp(); + const auto value = fpu.Get(0); + if (memory) StoreMemory(fpu, format, address, value); + else fpu.Set(index, value); + if (format & PS5LIFT_X87_POP) fpu.Pop(); + return 0; + } + case PS5LIFT_X87_ADD: + case PS5LIFT_X87_SUBTRACT: + case PS5LIFT_X87_SUBTRACT_REVERSE: + case PS5LIFT_X87_MULTIPLY: + case PS5LIFT_X87_DIVIDE: + case PS5LIFT_X87_DIVIDE_REVERSE: { + fpu.ClearRoundedUp(); + const auto destination = memory ? 0u : (format & PS5LIFT_X87_TO_REGISTER) ? index : 0u; + const auto left = fpu.Get(destination); + const auto right = memory ? LoadMemory(fpu, format, address) : fpu.Get(destination == 0 ? index : 0); + fpu.Set(destination, Compute(fpu, operation, left, right)); + if (!memory && (format & PS5LIFT_X87_TO_REGISTER) && (format & PS5LIFT_X87_POP)) fpu.Pop(); + return 0; + } + case PS5LIFT_X87_COMPARE: + case PS5LIFT_X87_COMPARE_UNORDERED: { + const auto right = memory ? LoadMemory(fpu, format, address) : fpu.Get(index); + const auto left = fpu.Get(0); + fpu.Operands(left, right); + fpu.SetConditions(CompareConditions(Compare(left, right, operation == PS5LIFT_X87_COMPARE_UNORDERED))); + if (format & (PS5LIFT_X87_POP | PS5LIFT_X87_POP_TWICE)) fpu.Pop(); + if (format & PS5LIFT_X87_POP_TWICE) fpu.Pop(); + return 0; + } + case PS5LIFT_X87_COMPARE_FLAGS: + case PS5LIFT_X87_COMPARE_FLAGS_UNORDERED: { + fpu.ClearRoundedUp(); + const auto left = fpu.Get(0); + const auto right = fpu.Get(index); + fpu.Operands(left, right); + const auto result = Compare(left, right, operation == PS5LIFT_X87_COMPARE_FLAGS_UNORDERED); + if (format & PS5LIFT_X87_POP) fpu.Pop(); + return result; + } + case PS5LIFT_X87_TEST: { + const auto value = fpu.Get(0); + fpu.Operands(value, Zero(false)); + fpu.SetConditions(CompareConditions(Compare(value, Zero(false), false))); + return 0; + } + case PS5LIFT_X87_EXAMINE: + fpu.SetConditions(Examine(fpu)); + return 0; + case PS5LIFT_X87_CHANGE_SIGN: { + fpu.ClearRoundedUp(); + auto value = fpu.Get(0); + value.signExp = static_cast(value.signExp ^ 0x8000); + fpu.Set(0, value); + return 0; + } + case PS5LIFT_X87_ABSOLUTE: { + fpu.ClearRoundedUp(); + auto value = fpu.Get(0); + value.signExp = static_cast(value.signExp & 0x7fff); + fpu.Set(0, value); + return 0; + } + case PS5LIFT_X87_SQUARE_ROOT: + case PS5LIFT_X87_ROUND: { + fpu.ClearRoundedUp(); + const auto value = fpu.Get(0); + fpu.Set(0, Compute(fpu, operation, value, value)); + return 0; + } + case PS5LIFT_X87_SCALE: { + const FullPrecision precision; + fpu.ClearRoundedUp(); + const auto value = fpu.Get(0); + const auto scale = fpu.Get(1); + fpu.Operands(value, scale); + if (IsNan(value) || IsNan(scale)) { + fpu.Set(0, extF80_add(value, scale)); + return 0; + } + if (IsInfinity(scale)) { + const bool up = !IsNegative(scale); + if (up ? IsZero(value) : IsInfinity(value)) { + fpu.Raise(StatusInvalid); + fpu.Set(0, Indefinite()); + } else { + fpu.Set(0, up ? Infinity(IsNegative(value)) : Zero(IsNegative(value))); + } + return 0; + } + constexpr std::int64_t Saturated = 0x10000; + std::int64_t power = IsNegative(scale) ? -Saturated : Saturated; + if ((scale.signExp & 0x7fff) < 0x3fff + 16) { + const auto saved = softfloat_exceptionFlags; + power = extF80_to_i64(scale, softfloat_round_minMag, false); + softfloat_exceptionFlags = saved; + } + const auto scaled = [&] { + auto result = value; + auto remaining = power; + const auto step = remaining > 0 ? 16000 : -16000; + while (remaining != 0) { + const auto chunk = (remaining > 0 ? remaining > step : remaining < step) ? step : remaining; + extFloat80_t factor; + factor.signExp = static_cast(0x3fff + chunk); + factor.signif = 0x8000000000000000ull; + result = extF80_mul(result, factor); + remaining -= chunk; + } + return result; + }; + fpu.Set(0, WithRoundUp(fpu, scaled)); + return 0; + } + case PS5LIFT_X87_PARTIAL_REMAINDER: + case PS5LIFT_X87_IEEE_REMAINDER: { + const FullPrecision precision; + const auto dividend = fpu.Get(0); + const auto divisor = fpu.Get(1); + fpu.Operands(dividend, divisor); + const auto nearest = extF80_rem(dividend, divisor); + auto truncated = nearest; + if (!IsNan(nearest) && (nearest.signif != 0 || (nearest.signExp & 0x7fff) != 0) && IsNegative(nearest) != IsNegative(dividend)) { + auto magnitude = divisor; + magnitude.signExp = static_cast((magnitude.signExp & 0x7fff) | (dividend.signExp & 0x8000)); + const auto savedFlags = softfloat_exceptionFlags; + truncated = extF80_add(nearest, magnitude); + softfloat_exceptionFlags = savedFlags; + } + const auto remainder = operation == PS5LIFT_X87_PARTIAL_REMAINDER ? truncated : nearest; + std::uint16_t conditions = IsNan(remainder) ? static_cast(fpu.Conditions() & ~StatusC2) : 0; + if (!IsNan(remainder) && (divisor.signExp & 0x7fff) != 0x7fff) { + auto bits = TruncatedQuotientBits(dividend, divisor); + const bool roundedUp = truncated.signif != nearest.signif || truncated.signExp != nearest.signExp; + if (operation == PS5LIFT_X87_IEEE_REMAINDER && roundedUp) bits = (bits + 1) & 7; + if (bits & 1) conditions |= StatusC1; + if (bits & 2) conditions |= StatusC3; + if (bits & 4) conditions |= StatusC0; + } + fpu.SetConditions(conditions); + fpu.Set(0, remainder); + return 0; + } + case PS5LIFT_X87_EXCHANGE: { + fpu.ClearRoundedUp(); + const auto top = fpu.Get(0); + const auto other = fpu.Get(index); + fpu.Set(0, other); + fpu.Set(index, top); + return 0; + } + case PS5LIFT_X87_FREE: + fpu.Free(index); + return 0; + case PS5LIFT_X87_INCREMENT_TOP: + fpu.ClearRoundedUp(); + fpu.SetTop(fpu.Top() + 1); + return 0; + case PS5LIFT_X87_DECREMENT_TOP: + fpu.ClearRoundedUp(); + fpu.SetTop(fpu.Top() - 1); + return 0; + case PS5LIFT_X87_MOVE_REGISTER: + fpu.Set(0, fpu.Get(index)); + return 0; + default: + ps5lift_fail("unknown x87 operation 0x%x", operation); + } +} + +extern "C" void ps5lift_extended_operation(Ps5LiftState* state, const std::uint32_t operation, const void* left, const void* right, void* result) { + Fpu fpu(state); + fpu.ClearRoundedUp(); + const auto first = LoadMemory(fpu, PS5LIFT_X87_FLOAT80, reinterpret_cast(left)); + const auto second = LoadMemory(fpu, PS5LIFT_X87_FLOAT80, reinterpret_cast(right)); + StoreMemory(fpu, PS5LIFT_X87_FLOAT80, reinterpret_cast(result), Compute(fpu, operation, first, second)); +} + +extern "C" void ps5lift_extended_load(Ps5LiftState* state, const std::uint32_t format, const void* source, void* result) { + Fpu fpu(state); + fpu.ClearRoundedUp(); + StoreMemory(fpu, PS5LIFT_X87_FLOAT80, reinterpret_cast(result), LoadMemory(fpu, format, reinterpret_cast(source))); +} + +extern "C" void ps5lift_extended_store(Ps5LiftState* state, const std::uint32_t format, const void* value, void* destination) { + Fpu fpu(state); + fpu.ClearRoundedUp(); + StoreMemory(fpu, format, reinterpret_cast(destination), LoadMemory(fpu, PS5LIFT_X87_FLOAT80, reinterpret_cast(value))); +} + +#if LDBL_MANT_DIG == 113 +static_assert(sizeof(long double) == sizeof(float128_t)); + +namespace { + +float128_t HostDefaultNan() { + std::fenv_t environment; + std::feholdexcept(&environment); + volatile long double zero = 0; + const long double nan = zero / zero; + std::fesetenv(&environment); + float128_t bits; + std::memcpy(&bits, &nan, sizeof(bits)); + return bits; +} + +} +#endif + +#ifdef PS5LIFT_HOST_LONG_DOUBLE_HOLDS_EXTENDED +extern "C" void ps5lift_extended_to_host(const void* extended, long double* host) { + std::memset(host, 0, sizeof(*host)); +#if LDBL_MANT_DIG == 64 + std::memcpy(host, extended, 10); +#else + const auto value = ReadExtended(extended); + float128_t converted; + if (IsNan(value)) { + converted.v[1] = static_cast(value.signExp) << 48 | (value.signif & 0x7fffffffffffffffull) >> 15; + converted.v[0] = value.signif << 49; + } else { + const auto flags = softfloat_exceptionFlags; + converted = extF80_to_f128(value); + softfloat_exceptionFlags = flags; + } + std::memcpy(host, &converted, sizeof(converted)); +#endif +} + +extern "C" void ps5lift_extended_from_host(const Ps5LiftState* state, const long double* host, void* extended) { +#if LDBL_MANT_DIG == 64 + (void)state; + std::memcpy(extended, host, 10); +#else + static const float128_t DefaultNan = HostDefaultNan(); + float128_t value; + std::memcpy(&value, host, sizeof(value)); + if (value.v[0] == DefaultNan.v[0] && value.v[1] == DefaultNan.v[1]) { + WriteExtended(extended, Indefinite()); + return; + } + const auto flags = softfloat_exceptionFlags; + const auto rounding = softfloat_roundingMode; + const auto tininess = softfloat_detectTininess; + softfloat_roundingMode = SoftFloatRounding(state->X87Control); + softfloat_detectTininess = softfloat_tininess_afterRounding; + const extFloat80_t converted = f128_to_extF80(value); + softfloat_exceptionFlags = flags; + softfloat_roundingMode = rounding; + softfloat_detectTininess = tininess; + WriteExtended(extended, converted); +#endif +} +#endif diff --git a/ps5lift/tools/ps5lift.py b/ps5lift/tools/ps5lift.py new file mode 100644 index 000000000000..a46d2f3e6824 --- /dev/null +++ b/ps5lift/tools/ps5lift.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Build ps5lift's runtime. + +`ps5lift.py runtime` builds the runtime for a target.""" +import argparse +import concurrent.futures +import fcntl +import hashlib +import os +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +ANYPS5 = ROOT.parent +SOFTFLOAT = ANYPS5 / "3rdparty" / "softfloat" + +RUNTIME_SOURCES = ["runtime/src/Runtime.cpp", "runtime/src/GuestCpu.cpp", "runtime/src/X87.cpp", "runtime/src/Jump.cpp", "runtime/src/Cpuid.cpp", "runtime/src/StringCompare.cpp", "runtime/src/NativeCall.cpp"] +MAIN_SOURCE = "runtime/src/Main.cpp" +SOFTFLOAT_FLAGS = [ + "-DSOFTFLOAT_FAST_INT64", "-DSOFTFLOAT_ROUND_ODD", "-DINLINE_LEVEL=5", "-DSOFTFLOAT_FAST_DIV32TO16", "-DSOFTFLOAT_FAST_DIV64TO32", + "-DTHREAD_LOCAL=_Thread_local", f"-I{SOFTFLOAT / 'build' / 'Linux-x86_64-GCC'}", f"-I{SOFTFLOAT / 'source' / '8086-SSE'}", f"-I{SOFTFLOAT / 'source' / 'include'}", +] + +DEFAULT_CPUS = {"aarch64-unknown-linux-gnu": "neoverse-v2"} + + +def softfloat_sources(): + return [ANYPS5 / line.strip() for line in (ROOT / "runtime" / "softfloat_sources.txt").read_text().splitlines() if line.strip()] + + +def fingerprint(*parts): + return hashlib.sha256("\0".join(map(str, parts)).encode()).hexdigest() + + +def dependencies(depfile): + body = depfile.read_text().replace("\\\n", " ").split(":", 1)[1] + return [Path(token.replace("\\ ", " ")) for token in re.split(r"(? built for path in dependencies(depfile)) + + +def compile_one(command, source, output, record): + output.parent.mkdir(parents=True, exist_ok=True) + result = subprocess.run([*command, "-MMD", "-MF", str(output.with_suffix(".d")), "-c", str(source), "-o", str(output)], capture_output=True, text=True) + if result.returncode != 0: + raise SystemExit(f"compiling {source} failed:\n{result.stderr}") + output.with_suffix(".cmd").write_text(record) + + +def compiler_version(compiler): + return subprocess.run([compiler, "--version"], capture_output=True, text=True, check=True).stdout + + +def cpu_flags(triple, cpu): + cpu = DEFAULT_CPUS.get(triple) if cpu is None else cpu + if cpu is None or cpu == "generic": + return [] + return [f"-march={cpu}"] if triple.startswith("x86_64") else [f"-mcpu={cpu}"] + + +def build_runtime(triple, cpu, output_directory, clang, clangxx, jobs): + output_directory = Path(output_directory) + library = output_directory / "libps5lift_runtime.so" + main_library = output_directory / "libps5lift_main.a" + target = [f"--target={triple}", "-O2", "-fPIC", *cpu_flags(triple, cpu)] + units = [] + for path in softfloat_sources(): + units.append(([clang, *target, *SOFTFLOAT_FLAGS], path, output_directory / "softfloat" / (path.stem + ".o"))) + for relative in [*RUNTIME_SOURCES, MAIN_SOURCE]: + path = ROOT / relative + units.append(([clangxx, *target, "-std=c++20", f"-I{ROOT / 'runtime' / 'include'}", f"-I{SOFTFLOAT / 'source' / 'include'}", "-DTHREAD_LOCAL=thread_local", "-DSOFTFLOAT_FAST_INT64", "-DLITTLEENDIAN=1"], path, output_directory / "runtime" / (path.stem + ".o"))) + versions = {compiler: compiler_version(compiler) for compiler in (clang, clangxx)} + units = [(command, source, output, fingerprint(versions[command[0]], *command, source)) for command, source, output in units] + shared = [clangxx, f"--target={triple}", "-shared", "-fuse-ld=lld", f"-Wl,-soname,{library.name}", "-pthread", "-ldl"] + archiver = subprocess.run([clang, "--print-prog-name=llvm-ar"], capture_output=True, text=True, check=True).stdout.strip() + outputs = ((library, units[:-1], lambda temporary, objects: [*shared, *objects, "-o", temporary]), + (main_library, units[-1:], lambda temporary, objects: [archiver, "crs", temporary, *objects])) + output_directory.mkdir(parents=True, exist_ok=True) + with open(output_directory / ".lock", "w") as lock: + fcntl.flock(lock, fcntl.LOCK_EX) + stale = [unit for unit in units if needs_build(unit[3], unit[2], unit[2].with_suffix(".d"))] + with concurrent.futures.ThreadPoolExecutor(jobs) as pool: + list(pool.map(lambda unit: compile_one(*unit), stale)) + for output, members, command in outputs: + objects = [str(unit[2]) for unit in members] + record = fingerprint(*command("", objects)) + listing = output.with_name(output.name + ".cmd") + if output.exists() and listing.exists() and listing.read_text() == record and max(unit[2].stat().st_mtime for unit in members) < output.stat().st_mtime: + continue + temporary = output.with_name(output.name + ".tmp") + temporary.unlink(missing_ok=True) + subprocess.run(command(str(temporary), objects), check=True) + temporary.replace(output) + listing.write_text(record) + return library, main_library + + +def runtime_command(args): + for path in build_runtime(args.triple, args.cpu, args.output, args.clang, args.clangxx, args.jobs): + print(path) + return 0 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + commands = parser.add_subparsers(dest="command", required=True) + + runtime = commands.add_parser("runtime", help="build the runtime for a target", description="Build the ps5lift runtime and SoftFloat as a shared library for a target, and its main as an archive") + runtime.add_argument("--triple", required=True) + runtime.add_argument("--cpu", help="CPU of the lifted code the runtime serves, as the lifter's --cpu takes it, generic and native included; the lifter's default CPU by default") + runtime.add_argument("--output", required=True) + runtime.add_argument("--clang", default="clang") + runtime.add_argument("--clangxx", default="clang++") + runtime.add_argument("--jobs", type=int, default=os.cpu_count()) + runtime.set_defaults(handler=runtime_command) + + args = parser.parse_args() + return args.handler(args) + + +if __name__ == "__main__": + sys.exit(main()) From 7ffeca38e0dfbdd826c5cbcb794043c51a60811c Mon Sep 17 00:00:00 2001 From: oberonlift Date: Mon, 2 Nov 2026 16:05:20 +0000 Subject: [PATCH 07/31] ci(ps5lift): build ps5lift's runtime on Linux arm64 A workflow builds the runtime with LLVM 18 on GitHub's ubuntu-24.04-arm runner every night, on request, and for pull requests that change ps5lift or the workflow. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ps5lift.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/ps5lift.yml diff --git a/.github/workflows/ps5lift.yml b/.github/workflows/ps5lift.yml new file mode 100644 index 000000000000..ba3eb5ce7657 --- /dev/null +++ b/.github/workflows/ps5lift.yml @@ -0,0 +1,32 @@ +name: ps5lift + +on: + schedule: + - cron: '23 4 * * *' + pull_request: + paths: + - ps5lift/** + - .github/workflows/ps5lift.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ps5lift-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + runtime: + runs-on: ubuntu-24.04-arm + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + - name: Check out SoftFloat + run: git submodule update --init --depth 1 3rdparty/softfloat + - name: Install the toolchain + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends clang-18 lld-18 llvm-18 python3 + - name: Build the runtime + run: python3 ps5lift/tools/ps5lift.py runtime --triple aarch64-unknown-linux-gnu --output build/runtime --clang /usr/lib/llvm-18/bin/clang --clangxx /usr/lib/llvm-18/bin/clang++ From 18675665b4c3e3b26db431b1fbac717181d7bce1 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Tue, 3 Nov 2026 16:05:01 +0000 Subject: [PATCH 08/31] feat(ps5lift): answer cpuid and xgetbv for AnyPS5's libraries GuestCpuCpuid and GuestCpuXgetbv give a library built for a lifted guest the PS5's answers that lifted code gets from cpuid and xgetbv, so it can pick the code paths the guest CPU supports. Co-Authored-By: Claude Opus 5.5 --- ps5lift/runtime/src/Cpuid.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ps5lift/runtime/src/Cpuid.cpp b/ps5lift/runtime/src/Cpuid.cpp index d31980416004..e7b33e5e6176 100644 --- a/ps5lift/runtime/src/Cpuid.cpp +++ b/ps5lift/runtime/src/Cpuid.cpp @@ -68,3 +68,15 @@ extern "C" std::uint64_t ps5lift_xgetbv(const std::uint64_t address, const std:: if (index > 1) ps5lift_fail("xgetbv register 0x%" PRIx32 " at 0x%" PRIx64 " has no PS5 value", index, address); return EnabledState; } + +extern "C" void GuestCpuCpuid_nid_no_patch(const std::uint32_t leaf, const std::uint32_t subleaf, std::uint32_t* const registers) { + const auto answer = Identify(0, leaf, subleaf); + registers[0] = answer.Eax; + registers[1] = answer.Ebx; + registers[2] = answer.Ecx; + registers[3] = answer.Edx; +} + +extern "C" std::uint64_t GuestCpuXgetbv_nid_no_patch(const std::uint32_t index) { + return ps5lift_xgetbv(0, index); +} From fe7f034c4aa713e7efe00c01135eff0074623d7c Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 4 Nov 2026 02:19:24 +0000 Subject: [PATCH 09/31] fix(libc): cast the category vtable entry away from const in every build Clang rejects a reinterpret_cast from const void* to a function pointer as casting away qualifiers, so only Apple builds cast the vtable entry to void* first, and a Clang build on Linux failed to compile libc. Every build now takes the const_cast form, which GCC compiles the same. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/src/exception/StandardExceptions.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 542653a23564..86b2ec55cfc8 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -266,11 +266,7 @@ static const char* APS5_VABI FutureCategoryName_nid_no_patch(const CategoryObjec static ErrorCode APS5_VABI CategoryCondition_nid_no_patch(const CategoryObject* self, int value) { return {0, value, self}; } static bool APS5_VABI CategoryEquivalentCondition_nid_no_patch(const CategoryObject* self, int value, const ErrorCode* condition) { using Condition = ErrorCode (APS5_VABI *)(const CategoryObject*, int); -#ifdef __APPLE__ const auto mapped = reinterpret_cast(const_cast(static_cast(self->vtable)[4]))(self, value); -#else - const auto mapped = reinterpret_cast(static_cast(self->vtable)[4])(self, value); -#endif return condition->category == mapped.category && condition->value == mapped.value; } static bool APS5_VABI CategoryEquivalentCode_nid_no_patch(const CategoryObject* self, const ErrorCode* code, int value) { From 43d45a6cab945879a4e0ff58a6431aebdc0b5178 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 17:41:58 +0000 Subject: [PATCH 10/31] feat(build): add ANYPS5_GUEST_LIFTED to build the libraries for ps5lift The libraries' lifted-guest code paths were only reachable by compiling their sources outside this build with the macro defined by hand, which cannot produce the generated and configured headers that libSceVideoOut, libSceSsl, libSceVorbisDec, libSceUsbd and libSceVideodec2 need. The option defines ANYPS5_GUEST_LIFTED for every target under core/libs, so the libs target builds all 368 libraries for a lifted guest on an aarch64 host. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 1 + core/libs/CMakeLists.txt | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 564a637fd82a..5cda339a1222 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,7 @@ option(APS5_ENABLE_TIMING_LOG "Write frame timings asynchronously to frame-timin option(APS5_ENABLE_TRACY "Build the Tracy profiler client and report performance timer scopes as Tracy zones" OFF) option(ANYPS5_LIBRESSL_ORACLE "Build guest_libressl_oracle, which compares libSceLibreSsl with LibreSSL 2.5.4 downloaded from ftp.openbsd.org" OFF) option(ANYPS5_USE_SYSTEM_DEPS "Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the bundled submodules" OFF) +option(ANYPS5_GUEST_LIFTED "Build the system libraries for a guest whose code ps5lift recompiled for the host" OFF) add_compile_definitions(APS5_ENABLE_TIMING_LOG=$) include(cmake/SystemDeps.cmake) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index f27caac1b08f..e697ec342fe6 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -11,6 +11,13 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() +if(ANYPS5_GUEST_LIFTED) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux" OR NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(FATAL_ERROR "ANYPS5_GUEST_LIFTED builds the libraries for Linux with Clang, not for ${CMAKE_SYSTEM_NAME} with ${CMAKE_CXX_COMPILER_ID}") + endif() + add_compile_definitions(ANYPS5_GUEST_LIFTED) +endif() + if(APPLE) set(ANYPS5_PRX_RPATH "@loader_path") else() From 65feea885ea19cfa136b51bb39a42cf207d1d9cb Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 17:36:31 +0000 Subject: [PATCH 11/31] build: take the FFmpeg package for the target's architecture The full build always asked ffmpeg-core for the x86-64 package, so in an aarch64 build every library that links FFmpeg (libSceAjm, libSceAudiodec, libSceAvPlayer, libSceVideodec2) failed to link. The architecture now follows CMAKE_SYSTEM_PROCESSOR. A processor with no package, and MinGW on arm64, which has none either, stop the configuration. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cda339a1222..68ee2a63dbb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -678,7 +678,13 @@ if(NOT ANYPS5_USE_SYSTEM_DEPS) endif() endif() endif() -set(ARCHITECTURE x86_64) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$") + set(ARCHITECTURE x86_64) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$" AND NOT MINGW) + set(ARCHITECTURE arm64) +else() + message(FATAL_ERROR "There is no FFmpeg package for ${CMAKE_SYSTEM_PROCESSOR} with this toolchain") +endif() add_subdirectory(3rdparty/ffmpeg-core) if(MINGW) target_link_libraries(ffmpeg INTERFACE bcrypt ole32 user32 ws2_32) From 4d6893d84389ab99589e8e1bd4097ef61fe26d17 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Sat, 31 Oct 2026 16:10:58 +0000 Subject: [PATCH 12/31] build(libs): link lifted libraries against the ps5lift runtime A lifted build's libraries call the GuestCpu functions that ps5lift's runtime provides. On Linux they resolved against the executable at load time; macOS binds every undefined symbol to a named library, so the libraries have to link the runtime. ANYPS5_LIFTED_RUNTIME names it, and libc links it publicly, so every library that links libc gets it. Co-Authored-By: Claude Opus 5.5 --- core/libs/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index e697ec342fe6..174e6dfe4274 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -16,6 +16,10 @@ if(ANYPS5_GUEST_LIFTED) message(FATAL_ERROR "ANYPS5_GUEST_LIFTED builds the libraries for Linux with Clang, not for ${CMAKE_SYSTEM_NAME} with ${CMAKE_CXX_COMPILER_ID}") endif() add_compile_definitions(ANYPS5_GUEST_LIFTED) + set(ANYPS5_LIFTED_RUNTIME "" CACHE FILEPATH "The ps5lift runtime library that provides the GuestCpu functions") + if(NOT EXISTS "${ANYPS5_LIFTED_RUNTIME}") + message(FATAL_ERROR "ANYPS5_GUEST_LIFTED needs ANYPS5_LIFTED_RUNTIME, the ps5lift runtime library the libraries link against, not \"${ANYPS5_LIFTED_RUNTIME}\"") + endif() endif() if(APPLE) @@ -120,6 +124,9 @@ if(WIN32) endif() configure_windows_unwind(libc) configure_macos_unwind(libc) +if(ANYPS5_GUEST_LIFTED) + target_link_libraries(libc PUBLIC ${ANYPS5_LIFTED_RUNTIME}) +endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_options(libc PRIVATE LINKER:-z,now) target_link_options(libc INTERFACE LINKER:--no-as-needed) From d376d4457eb3c1e179ac52d0ba6fac98e1a97755 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 06:19:09 +0000 Subject: [PATCH 13/31] refactor(libc): build the x86-only helpers portably for a lifted guest Under ANYPS5_GUEST_LIFTED GuestCpu's helpers call GuestCpu*_nid_no_patch functions, which ps5lift's runtime implements on the guest's emulated CPU state: the fenv functions, the timestamp counter, cpuid and xgetbv then see the guest's FPU and the PS5's CPU. The soft-float builtins compute in the default floating-point environment through and pick NaN results as x86 does, the 16-byte atomics use __atomic_compare_exchange_n, Bessel uses std::sqrt and std::fma, and the x86 call-trace thunks and the setjmp/longjmp assembly are left to the host runtime, which owns the guest registers. Direct builds are unchanged. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 59 +++++++++++- core/libs/prx/libc/src/AtomicBuiltins.cpp | 10 ++ core/libs/prx/libc/src/Bessel.cpp | 19 +++- core/libs/prx/libc/src/FloatBuiltins.cpp | 92 +++++++++++++++++++ core/libs/prx/libc/src/JumpBuffer.cpp | 4 + core/libs/prx/libc/src/MathExact.cpp | 22 +++++ .../libc/src/specifics/x86_64/CallTrace.cpp | 2 +- 7 files changed, 204 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp index 53598c1a7b5b..ab56cd9252e7 100644 --- a/core/libs/prx/libc/include/GuestCpu.hpp +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -3,8 +3,63 @@ #include #include +#include + +#ifdef ANYPS5_GUEST_LIFTED + +extern "C" { +std::uint32_t GuestCpuMxcsr_nid_no_patch(void); +void GuestCpuSetMxcsr_nid_no_patch(std::uint32_t value); +std::uint16_t GuestCpuX87Control_nid_no_patch(void); +void GuestCpuSetX87Control_nid_no_patch(std::uint16_t value); +std::uint16_t GuestCpuX87Status_nid_no_patch(void); +void GuestCpuSetX87Status_nid_no_patch(std::uint16_t value); +void GuestCpuX87Wait_nid_no_patch(void); +std::uint32_t GuestCpuMxcsrMask_nid_no_patch(void); +std::uint64_t GuestCpuReadTsc_nid_no_patch(void); +void GuestCpuCpuid_nid_no_patch(std::uint32_t leaf, std::uint32_t subleaf, std::uint32_t* registers); +std::uint64_t GuestCpuXgetbv_nid_no_patch(std::uint32_t index); +} + +namespace GuestCpu { + +inline std::uint32_t Mxcsr() { return GuestCpuMxcsr_nid_no_patch(); } +inline void SetMxcsr(const std::uint32_t value) { GuestCpuSetMxcsr_nid_no_patch(value); } +inline std::uint16_t X87Control() { return GuestCpuX87Control_nid_no_patch(); } +inline void SetX87Control(const std::uint16_t value) { GuestCpuSetX87Control_nid_no_patch(value); } +inline std::uint16_t X87Status() { return GuestCpuX87Status_nid_no_patch(); } +inline void SetX87Status(const std::uint16_t value) { GuestCpuSetX87Status_nid_no_patch(value); } +inline void X87ClearExceptions() { SetX87Status(static_cast(X87Status() & 0x7f00)); } +inline void X87Wait() { GuestCpuX87Wait_nid_no_patch(); } +inline std::uint32_t MxcsrMask() { return GuestCpuMxcsrMask_nid_no_patch(); } +inline std::uint64_t ReadTsc() { return GuestCpuReadTsc_nid_no_patch(); } + +inline std::array Cpuid(const std::uint32_t leaf, const std::uint32_t subleaf) { + std::array registers; + GuestCpuCpuid_nid_no_patch(leaf, subleaf, registers.data()); + return registers; +} + +inline std::uint64_t Xgetbv(const std::uint32_t index) { return GuestCpuXgetbv_nid_no_patch(index); } + +inline std::array X87Environment() { + return {0xffff0000u | X87Control(), 0xffff0000u | X87Status(), 0xffffffffu, 0, 0, 0, 0xffff0000u}; +} + +inline void SetX87Environment(const std::array& environment) { + SetX87Control(static_cast(environment[0])); + SetX87Status(static_cast(environment[1])); +} + +inline void RaiseInvalidForSignalingNan(const float value) { + std::uint32_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + if ((bits & 0x7fffffffu) > 0x7f800000u && (bits & 0x400000u) == 0) SetMxcsr(Mxcsr() | 1u); +} + +} -#if defined(__x86_64__) +#elif defined(__x86_64__) #include #include @@ -82,7 +137,7 @@ inline void RaiseInvalidForSignalingNan(const float value) { } #else -#error "GuestCpu.hpp supports only x86-64 targets" +#error "GuestCpu.hpp needs an x86-64 target or ANYPS5_GUEST_LIFTED" #endif #endif diff --git a/core/libs/prx/libc/src/AtomicBuiltins.cpp b/core/libs/prx/libc/src/AtomicBuiltins.cpp index bca7b11a4da7..e3cee1669870 100644 --- a/core/libs/prx/libc/src/AtomicBuiltins.cpp +++ b/core/libs/prx/libc/src/AtomicBuiltins.cpp @@ -12,6 +12,14 @@ namespace { throw std::runtime_error(std::string(function) + ": the console implementation traps (ud2)"); } +#ifdef ANYPS5_GUEST_LIFTED + +bool CompareExchange16(Uint128* target, Uint128& expected, Uint128 desired) { + return __atomic_compare_exchange_n(target, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); +} + +#else + bool CompareExchange16(Uint128* target, Uint128& expected, Uint128 desired) { std::uint64_t low = static_cast(expected); std::uint64_t high = static_cast(expected >> 64); @@ -24,6 +32,8 @@ bool CompareExchange16(Uint128* target, Uint128& expected, Uint128 desired) { return exchanged; } +#endif + Uint128 ReadHalves(const Uint128* target) { const auto* halves = reinterpret_cast(target); const std::uint64_t low = halves[0]; diff --git a/core/libs/prx/libc/src/Bessel.cpp b/core/libs/prx/libc/src/Bessel.cpp index 3ef4643d2a23..06eeb5c79572 100644 --- a/core/libs/prx/libc/src/Bessel.cpp +++ b/core/libs/prx/libc/src/Bessel.cpp @@ -3,9 +3,12 @@ #include #include #include -#include #include +#ifndef ANYPS5_GUEST_LIFTED +#include +#endif + #include "prx/libc/include/general/VabiMacros.hpp" namespace { @@ -103,6 +106,18 @@ double Signed(std::uint32_t value) { return static_cast(static_cast(value)); } +#ifdef ANYPS5_GUEST_LIFTED + +double SquareRoot(double x) { + return std::sqrt(x); +} + +double Fused(double a, double b, double c) { + return std::fma(a, b, c); +} + +#else + double SquareRoot(double x) { return _mm_cvtsd_f64(_mm_sqrt_sd(_mm_setzero_pd(), _mm_set_sd(x))); } @@ -120,6 +135,8 @@ double Fused(double a, double b, double c) { return HardwareFma ? HardwareFused(a, b, c) : std::fma(a, b, c); } +#endif + template double Horner(double z, const double (&coefficients)[Count]) { double result = coefficients[Count - 1]; diff --git a/core/libs/prx/libc/src/FloatBuiltins.cpp b/core/libs/prx/libc/src/FloatBuiltins.cpp index 235c74f27a4c..020d73eb1962 100644 --- a/core/libs/prx/libc/src/FloatBuiltins.cpp +++ b/core/libs/prx/libc/src/FloatBuiltins.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/Api.hpp" #include +#include #include #include #include @@ -213,6 +214,77 @@ TFloat GeneratedNan(TFloat result, TFloat a, TFloat b) { return IsNan(result) && !IsNan(a) && !IsNan(b) ? QuietNan() : result; } +#ifdef ANYPS5_GUEST_LIFTED + +template +TFloat QuietOperand(TFloat value) { + using Rep = typename FloatFormat::Rep; + return std::bit_cast(static_cast(std::bit_cast(value) | (static_cast(1) << (FloatFormat::SignificandBits - 1)))); +} + +template +TFloat DefaultNan() { + using Rep = typename FloatFormat::Rep; + return Negate(std::bit_cast(std::bit_cast(QuietNan()))); +} + +template +TResult InStandardEnvironment(int rounding, TOperation operation) { + std::fenv_t saved; + std::fegetenv(&saved); + std::fesetenv(FE_DFL_ENV); + std::fesetround(rounding); + const volatile TResult result = operation(); + std::fesetenv(&saved); + return result; +} + +template +TFloat Standard(TFloat a, TFloat b, int rounding, TOperation operation) { + if (IsNan(a)) return QuietOperand(a); + if (IsNan(b)) return QuietOperand(b); + const TFloat result = InStandardEnvironment(rounding, [&] { + const volatile TFloat left = a; + const volatile TFloat right = b; + return operation(left, right); + }); + return IsNan(result) ? DefaultNan() : result; +} + +double AddDouble(double a, double b) { + return Standard(a, b, FE_TONEAREST, [](double x, double y) { return x + y; }); +} + +double MultiplyDouble(double a, double b) { + return Standard(a, b, FE_TONEAREST, [](double x, double y) { return x * y; }); +} + +double DivideDouble(double a, double b) { + return Standard(a, b, FE_TONEAREST, [](double x, double y) { return x / y; }); +} + +float AddFloat(float a, float b) { + return Standard(a, b, FE_TONEAREST, [](float x, float y) { return x + y; }); +} + +float MultiplyFloat(float a, float b) { + return Standard(a, b, FE_TONEAREST, [](float x, float y) { return x * y; }); +} + +float DivideFloat(float a, float b) { + return Standard(a, b, FE_TONEAREST, [](float x, float y) { return x / y; }); +} + +double DivideDoubleTowardZero(double a, double b) { + return Standard(a, b, FE_TOWARDZERO, [](double x, double y) { return x / y; }); +} + +float DivideFloatTowardZero(float a, float b) { + return Standard(a, b, FE_TOWARDZERO, [](float x, float y) { return x / y; }); +} + +#else + double AddDouble(double a, double b) { std::uint32_t saved = 0; asm volatile("stmxcsr %[saved]\n\tldmxcsr %[standard]\n\taddsd %[b], %[a]\n\tldmxcsr %[saved]" @@ -271,6 +343,8 @@ float DivideFloatTowardZero(float a, float b) { return a; } +#endif + template TFloat FlushSubnormalQuotient(TFloat quotient, TFloat truncated) { using Format = FloatFormat; @@ -281,6 +355,22 @@ TFloat FlushSubnormalQuotient(TFloat quotient, TFloat truncated) { return std::bit_cast(static_cast(std::bit_cast(truncated) & signBit)); } +#ifdef ANYPS5_GUEST_LIFTED + +float TruncateDouble(double value) { + if (IsNan(value)) { + const std::uint64_t bits = std::bit_cast(value); + const auto sign = static_cast(bits >> 32) & 0x80000000u; + return std::bit_cast(sign | 0x7fc00000u | static_cast((bits & 0xfffffffffffffull) >> 29)); + } + return InStandardEnvironment(FE_TONEAREST, [&] { + const volatile double source = value; + return static_cast(source); + }); +} + +#else + float TruncateDouble(double value) { std::uint32_t saved = 0; float result; @@ -289,6 +379,8 @@ float TruncateDouble(double value) { return result; } +#endif + double ExtendFloat(float value) { const std::uint32_t bits = std::bit_cast(value); const std::uint64_t sign = static_cast(bits >> 31) << 63; diff --git a/core/libs/prx/libc/src/JumpBuffer.cpp b/core/libs/prx/libc/src/JumpBuffer.cpp index 187225c8f1b3..75cd4c35d068 100644 --- a/core/libs/prx/libc/src/JumpBuffer.cpp +++ b/core/libs/prx/libc/src/JumpBuffer.cpp @@ -46,6 +46,8 @@ void APS5_VABI JumpBufferRestoreMask_nid_no_patch(const unsigned char* buffer) { // return address, rbx, rsp, rbp, r12-r15, MXCSR, the x87 control word and, as FreeBSD's do, the // signal mask at 72 with the flag sigsetjmp keeps at 88, once libkernel has registered sigprocmask. +#ifndef ANYPS5_GUEST_LIFTED + #if defined(_WIN32) || defined(__APPLE__) #define APS5_JUMP_ASM_CALL(name) " call " APS5_ASM_SYMBOL(name) "\n" #else @@ -146,3 +148,5 @@ asm(".text\n" " mov 56(%rdi), %r15\n" " fldcw 68(%rdi)\n" " jmp *0(%rdi)\n"); + +#endif diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp index 7423ded1364a..4713849442d2 100644 --- a/core/libs/prx/libc/src/MathExact.cpp +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -6,7 +6,9 @@ #include #include #include +#if defined(__SSE2__) #include +#endif #include #include @@ -257,6 +259,16 @@ TFloat FusedPolynomial(TFloat x, const TFloat* table, int degree) { return result; } +#if !defined(__SSE2__) +std::int64_t ConvertToInteger(double x) { + if (!(x >= -0x1p63 && x < 0x1p63)) { + std::feraiseexcept(FE_INVALID); + return std::numeric_limits::min(); + } + return static_cast(std::rint(x)); +} +#endif + } extern "C" { @@ -362,6 +374,7 @@ float APS5_VABI rintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std: float APS5_VABI nearbyintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::rintf(x); } double APS5_VABI nearbyint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::rint(x); } +#if defined(__SSE2__) std::int64_t APS5_VABI lrint_nid_postfix(double x) { return _mm_cvtsd_si64(_mm_set_sd(x)); } std::int64_t APS5_VABI lrintf_nid_postfix(float x) { return _mm_cvtss_si64(_mm_set_ss(x)); } @@ -369,6 +382,15 @@ std::int64_t APS5_VABI lrintf_nid_postfix(float x) { return _mm_cvtss_si64(_mm_s long long APS5_VABI llrint_nid_postfix(double x) { return _mm_cvtsd_si64(_mm_set_sd(x)); } long long APS5_VABI llrintf_nid_postfix(float x) { return _mm_cvtss_si64(_mm_set_ss(x)); } +#else +std::int64_t APS5_VABI lrint_nid_postfix(double x) { return ConvertToInteger(x); } + +std::int64_t APS5_VABI lrintf_nid_postfix(float x) { return ConvertToInteger(x); } + +long long APS5_VABI llrint_nid_postfix(double x) { return ConvertToInteger(x); } + +long long APS5_VABI llrintf_nid_postfix(float x) { return ConvertToInteger(x); } +#endif double APS5_VABI drem_nid_postfix(double x, double y) { return Remainder(x, y); diff --git a/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp b/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp index c6013bb4db61..2b585ab4cc68 100644 --- a/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp +++ b/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp @@ -1,4 +1,4 @@ -#if defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__)) +#if !defined(ANYPS5_GUEST_LIFTED) && (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__))) #ifdef _WIN32 #include #elif defined(__APPLE__) From 046371de3fc21153a5daf3c20e2eb65ef61025a3 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Mon, 2 Nov 2026 16:28:59 +0000 Subject: [PATCH 14/31] build(libs): find the ps5lift runtime from the lifted libraries The libraries link ps5lift's runtime, but their RPATH named only their own directory, so a library that a process loads with dlopen, as some tests load libc, could not find the runtime. In a lifted build their RPATH also names the runtime's directory. Co-Authored-By: Claude Opus 5.5 --- core/libs/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 174e6dfe4274..38bc384154e2 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -27,6 +27,10 @@ if(APPLE) else() set(ANYPS5_PRX_RPATH "$ORIGIN") endif() +if(ANYPS5_GUEST_LIFTED) + get_filename_component(ANYPS5_LIFTED_RUNTIME_DIRECTORY "${ANYPS5_LIFTED_RUNTIME}" DIRECTORY) + list(APPEND ANYPS5_PRX_RPATH "${ANYPS5_LIFTED_RUNTIME_DIRECTORY}") +endif() add_executable(nid_patcher ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/Sha1.cpp From d61c2e2119130d466c15b7675b5cba15550b46b8 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Mon, 2 Nov 2026 16:28:59 +0000 Subject: [PATCH 15/31] test(libs): hand the ps5lift runtime an empty program in lifted tests The runtime refuses to describe the guest images until the executable hands it its lifted program, and a test program under core/libs is a native program with none, so libraries that look up guest images stopped. In a lifted build the test programs there link an object that hands the runtime an empty program, and link the runtime. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 3 +++ core/libs/CMakeLists.txt | 4 ++++ core/libs/tests/LiftedEmptyProgram.cpp | 8 ++++++++ 3 files changed, 15 insertions(+) create mode 100644 core/libs/tests/LiftedEmptyProgram.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 68ee2a63dbb9..35603585994a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,9 @@ function(add_test_executable target) target_sources(${target} PRIVATE $) target_link_options(${target} PRIVATE LINKER:--wrap=main) endif() + if(ANYPS5_LIFTED_TEST_PROGRAM) + target_link_libraries(${target} PRIVATE ${ANYPS5_LIFTED_TEST_PROGRAM}) + endif() endfunction() function(configure_host_test_executable target) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 38bc384154e2..baedb7200239 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -30,6 +30,10 @@ endif() if(ANYPS5_GUEST_LIFTED) get_filename_component(ANYPS5_LIFTED_RUNTIME_DIRECTORY "${ANYPS5_LIFTED_RUNTIME}" DIRECTORY) list(APPEND ANYPS5_PRX_RPATH "${ANYPS5_LIFTED_RUNTIME_DIRECTORY}") + add_library(anyps5_lifted_test_program OBJECT ${CMAKE_CURRENT_SOURCE_DIR}/tests/LiftedEmptyProgram.cpp) + target_include_directories(anyps5_lifted_test_program PRIVATE ${CMAKE_SOURCE_DIR}/ps5lift/runtime/include) + target_link_libraries(anyps5_lifted_test_program INTERFACE ${ANYPS5_LIFTED_RUNTIME}) + set(ANYPS5_LIFTED_TEST_PROGRAM anyps5_lifted_test_program) endif() add_executable(nid_patcher diff --git a/core/libs/tests/LiftedEmptyProgram.cpp b/core/libs/tests/LiftedEmptyProgram.cpp new file mode 100644 index 000000000000..e96355b39045 --- /dev/null +++ b/core/libs/tests/LiftedEmptyProgram.cpp @@ -0,0 +1,8 @@ +#include + +namespace { + +const Ps5LiftProgram EmptyProgram = {}; +const bool Used = (ps5lift_use_program(&EmptyProgram), true); + +} From 868d77d76dc73bf1da5c2604ac92bfd18aec73cb Mon Sep 17 00:00:00 2001 From: oberonlift Date: Mon, 2 Nov 2026 16:43:05 +0000 Subject: [PATCH 16/31] ci(ps5lift): run AnyPS5's tests that pass in a lifted build on Linux arm64 The workflow builds the runtime, configures AnyPS5 with ANYPS5_GUEST_LIFTED on GitHub's ubuntu-24.04-arm runner and runs the tests that ps5lift/tests/lifted_tests.txt names on lavapipe: tools/lifted_tests.py builds the files their commands name and runs them with ctest. The 101 tests that pass with this change are the ones whose programs do not link libc, such as the relinker's and the shader recompiler's. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ps5lift.yml | 35 ++++++++++++ ps5lift/tests/lifted_tests.txt | 101 +++++++++++++++++++++++++++++++++ ps5lift/tools/lifted_tests.py | 60 ++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 ps5lift/tests/lifted_tests.txt create mode 100755 ps5lift/tools/lifted_tests.py diff --git a/.github/workflows/ps5lift.yml b/.github/workflows/ps5lift.yml index ba3eb5ce7657..72d69878ed4e 100644 --- a/.github/workflows/ps5lift.yml +++ b/.github/workflows/ps5lift.yml @@ -30,3 +30,38 @@ jobs: sudo apt-get install -y --no-install-recommends clang-18 lld-18 llvm-18 python3 - name: Build the runtime run: python3 ps5lift/tools/ps5lift.py runtime --triple aarch64-unknown-linux-gnu --output build/runtime --clang /usr/lib/llvm-18/bin/clang --clangxx /usr/lib/llvm-18/bin/clang++ + + libraries: + runs-on: ubuntu-24.04-arm + timeout-minutes: 300 + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 5G + CMAKE_C_COMPILER_LAUNCHER: ccache + CMAKE_CXX_COMPILER_LAUNCHER: ccache + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + - name: Install the toolchain, the libraries and lavapipe + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends cmake ninja-build ccache python3 clang-18 lld-18 llvm-18 libx11-dev libxext-dev libxrandr-dev libwayland-dev libxkbcommon-dev libegl-dev libdecor-0-dev libpipewire-0.3-dev libpulse-dev libasound2-dev libvulkan1 mesa-vulkan-drivers + - uses: actions/cache/restore@v6 + with: + path: .ccache + key: lifted-libraries-ccache-${{ github.sha }} + restore-keys: lifted-libraries-ccache- + - name: Build the runtime + run: python3 ps5lift/tools/ps5lift.py runtime --triple aarch64-unknown-linux-gnu --output build/runtime --clang /usr/lib/llvm-18/bin/clang --clangxx /usr/lib/llvm-18/bin/clang++ + - name: Configure AnyPS5 for a lifted guest + run: cmake -S . -B build/anyps5 -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_GUEST_LIFTED=ON -DANYPS5_LIFTED_RUNTIME=$PWD/build/runtime/libps5lift_runtime.so -DCMAKE_C_COMPILER=/usr/lib/llvm-18/bin/clang -DCMAKE_CXX_COMPILER=/usr/lib/llvm-18/bin/clang++ -DCMAKE_C_FLAGS=-mcpu=neoverse-v2 -DCMAKE_CXX_FLAGS=-mcpu=neoverse-v2 + - name: Build and run the tests that pass in a lifted build + run: python3 ps5lift/tools/lifted_tests.py --build build/anyps5 --jobs 4 + - if: always() + run: ccache --show-stats + - if: always() + uses: actions/cache/save@v6 + with: + path: .ccache + key: lifted-libraries-ccache-${{ github.sha }} diff --git a/ps5lift/tests/lifted_tests.txt b/ps5lift/tests/lifted_tests.txt new file mode 100644 index 000000000000..6ca152c53c5d --- /dev/null +++ b/ps5lift/tests/lifted_tests.txt @@ -0,0 +1,101 @@ +MemoryPoolTest +agc_bda_atomics +agc_capture_trace_flush_failure +agc_capture_trace_write_failure +agc_draw_inputs_in_place_driver +agc_driver_draw_program_trace +agc_driver_registration_prepare_budget +agc_float32_flush_control +agc_fragment_barycentric +agc_frame_limit +agc_host_report +agc_host_report_vulkan +agc_pipeline_link_optimization +agc_raster_order_group +agc_snapshot_copy_failure +agc_subgroup_clock +agc_tessellation_pipeline_cache +agc_unit_flags_allocation_failure_empty +agc_unit_flags_allocation_failure_populated +amd64_only_converter +andn_lowering +atrac9_config_data +atrac9_gradient +autorun_exit_status +bitfield_lowering +bls_lowering +buffer_bounds +call_registry_writer +call_site_index +call_site_resolver +cli_args +code_data_scanning +console_progress +control_flow_graph +cpu_topology +crash_text +decoder_length +elf_reader_bounds +entry_stub +error_dialog_request +file_identity +file_reader_prefix +file_writer_binary +file_writer_text +guest_freetype_pfr_null_face +guest_fstatvfs_conversion +guest_getlogin_no_session +guest_json_numeric_locale_setup +guest_load_ranges +guest_socket_error +guest_utime_forwarding +host_performance_game_mode +host_performance_mac_activity +host_performance_session +host_thread_local +ime_composition +ime_dialog_pad_keyboard +ime_host_text +linux_desktop_entry +linux_load_pages +masked_store_lowering +msg_dialog_param +msg_dialog_request +msg_dialog_text +mulx_lowering +native_path +nid_buffer_validation +pad_command_file +pad_host_volume +pad_identify +pad_keyboard_mouse +pad_rumble_intensity +pad_status_sdl +param_json_utf8 +pe_import_first +program_layout_ranges +psn_dialog_notice_mode +red_zone_framer +relinker_conversion +relinker_game_patch +relr_relocations +request_serializer +rewrite_boundaries +runtime_layout +savedata_replace_file_failure +sdk_mman_syscall +section_header_ranges +shader_cache_file_read +shader_ir_use_rewrite +shader_ssa_pass_reads +shift_lowering +spanned_uniformity +strict_nid_filter +symlink_file_writer_binary +symlink_file_writer_text +system_service_language_locale +unwind_record_bounds +video_out_port0_presence +windows_entry_stub_bounds +windows_icon_resource +windows_trampoline_builder diff --git a/ps5lift/tools/lifted_tests.py b/ps5lift/tools/lifted_tests.py new file mode 100755 index 000000000000..98e22573833c --- /dev/null +++ b/ps5lift/tools/lifted_tests.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Build and run AnyPS5's tests that pass in a build for a lifted guest. + +`lifted_tests.py --build ` builds the tests that tests/lifted_tests.txt names in an AnyPS5 build configured +with Ninja, ANYPS5_GUEST_LIFTED and BUILD_TESTING, and runs them with ctest.""" +import argparse +import json +import os +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +TESTS = ROOT / "tests" / "lifted_tests.txt" + + +def output(command): + return subprocess.run(command, check=True, capture_output=True, text=True).stdout + + +def numbers(build): + tests = json.loads(output(["ctest", "--test-dir", build, "--show-only=json-v1"]))["tests"] + return {test["name"]: number for number, test in enumerate(tests, 1)} + + +def commands(build): + found = {} + for testfile in Path(build).rglob("CTestTestfile.cmake"): + for line in testfile.read_text().splitlines(): + if match := re.fullmatch(r'add_test\(\[=\[(.+?)\]=\] (.*)\)', line): + found[match.group(1)] = re.findall(r'"((?:[^"\\]|\\.)*)"', match.group(2)) + return found + + +def targets_for(build, names): + outputs = {line.split(":", 1)[0] for line in output(["ninja", "-C", build, "-t", "targets", "all"]).splitlines()} + tests = commands(build) + return sorted({path for name in names for token in re.findall(r"[^\s'\"]+", " ".join(tests[name])) + if (path := os.path.relpath(token, build)) in outputs}) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--build", required=True, help="AnyPS5's build directory") + parser.add_argument("--jobs", type=int, default=os.cpu_count()) + args = parser.parse_args() + names = TESTS.read_text().split() + tests = numbers(args.build) + unknown = [name for name in names if name not in tests] + if unknown: + raise SystemExit(f"{TESTS} names tests that {args.build} does not register: {' '.join(unknown)}") + subprocess.run(["ninja", "-C", args.build, "-j", str(args.jobs), *targets_for(args.build, names)], check=True) + selection = "0,0,0," + ",".join(str(tests[name]) for name in names) + return subprocess.run(["ctest", "--test-dir", args.build, "-I", selection, "--parallel", str(args.jobs), "--timeout", "30", + "--output-on-failure", "--no-tests=error"]).returncode + + +if __name__ == "__main__": + sys.exit(main()) From b4880be55f6efa5e036781e7e3879a9e57a68f29 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Fri, 6 Nov 2026 15:45:08 +0000 Subject: [PATCH 17/31] fix(libSceNKWebKit): leave the forwarded exports out of lifted builds ForwardExports.cpp defines each forwarded export as an x86 jmp to the export it aliases, which does not assemble for aarch64. Lifted builds leave the file out: a lifted guest reaches an export only through an import bridge generated from its C declaration, and these aliases have none. The debt file lists the gap. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNKWebKit/CMakeLists.txt | 3 +++ docs/dev/debt/libSceNKWebKit.md | 1 + 2 files changed, 4 insertions(+) diff --git a/core/libs/prx/libSceNKWebKit/CMakeLists.txt b/core/libs/prx/libSceNKWebKit/CMakeLists.txt index af9128bc4e8f..a530c2cdc34d 100644 --- a/core/libs/prx/libSceNKWebKit/CMakeLists.txt +++ b/core/libs/prx/libSceNKWebKit/CMakeLists.txt @@ -18,6 +18,9 @@ endif() install(FILES ${CMAKE_SOURCE_DIR}/3rdparty/quickjs/LICENSE DESTINATION licenses/quickjs) file(GLOB nkWebKitSources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) +if(ANYPS5_GUEST_LIFTED) + list(REMOVE_ITEM nkWebKitSources ${CMAKE_CURRENT_SOURCE_DIR}/src/ForwardExports.cpp) +endif() add_library(libSceNKWebKit SHARED EXCLUDE_FROM_ALL Export.cpp DateParsing.cpp CharactersToNumber.cpp ${nkWebKitSources}) target_include_directories(libSceNKWebKit PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(libSceNKWebKit PUBLIC libSceJsc PRIVATE qjs libc) diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 0fd731ae606a..97637e059b5f 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -20,6 +20,7 @@ Inventory prose retains historical counts; use the [generated progress report](h ### Functional - [WKBundleScriptWorld query and name functions](../../../core/libs/prx/libSceNKWebKit/src/BundleScriptWorld.cpp) (libSceNKWebKit) - isolated worlds are test-driven until script context execution is connected. +- [Forwarded exports](../../../core/libs/prx/libSceNKWebKit/src/ForwardExports.cpp) (libSceNKWebKit, `ANYPS5_GUEST_LIFTED` builds) - are left out of lifted builds. Each is an x86 `jmp` to the export it aliases, and a lifted guest reaches an export only through an import bridge generated from its C declaration, which these assembly-only aliases do not have. Closes when: the aliases have C declarations - [WTF main thread and run loop identification](../../../core/libs/prx/libSceNKWebKit/src/WtfMainThread.cpp) (libSceNKWebKit) - `initializeMainThread`, `isMainThread`, `isMainRunLoop` and `isMainThreadOrGCThread` identify the initialising thread as the WebKit main thread and query whether the current thread is the main thread; garbage-collection worker threads are unmodelled and console execution is unverified. - [User content URL pattern matching](../../../core/libs/prx/libSceNKWebKit/src/UserContentURLPattern.cpp) (libSceNKWebKit) - `WKUserContentURLPatternMatchesURL` parses pattern components (scheme, host, path) and evaluates wildcard patterns against candidate URLs per WebKit `UserContentURLPattern` semantics; port matching, non-standard schemes and console execution are unverified. From e9ec7340cff2eca3bec29084b52bfa5532f0c2c7 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 08:45:16 +0000 Subject: [PATCH 18/31] feat(libc): unwind guest frames from the lifted guest state In a lifted build the DWARF unwinder starts from the guest frame that called the import, finds frames through the unwind tables of the guest images, which dl_iterate_phdr lists through the program's GuestCpuIterateImages, resolves the personality routine from its import name and installs a landing pad by handing its registers back to the guest CPU, through new GuestCpu functions. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 3 ++ .../libc/include/specifics/linux/ElfTypes.hpp | 5 +++ core/libs/prx/libc/src/exception/Unwind.cpp | 37 +++++++++++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp index ab56cd9252e7..f5d4eebb401b 100644 --- a/core/libs/prx/libc/include/GuestCpu.hpp +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -16,6 +16,9 @@ std::uint16_t GuestCpuX87Status_nid_no_patch(void); void GuestCpuSetX87Status_nid_no_patch(std::uint16_t value); void GuestCpuX87Wait_nid_no_patch(void); std::uint32_t GuestCpuMxcsrMask_nid_no_patch(void); +void GuestCpuUnwindStart_nid_no_patch(std::uint64_t* registers); +const char* GuestCpuImportName_nid_no_patch(std::uint64_t address); +[[noreturn]] void GuestCpuUnwindResume_nid_no_patch(const std::uint64_t* registers); std::uint64_t GuestCpuReadTsc_nid_no_patch(void); void GuestCpuCpuid_nid_no_patch(std::uint32_t leaf, std::uint32_t subleaf, std::uint32_t* registers); std::uint64_t GuestCpuXgetbv_nid_no_patch(std::uint32_t index); diff --git a/core/libs/prx/libc/include/specifics/linux/ElfTypes.hpp b/core/libs/prx/libc/include/specifics/linux/ElfTypes.hpp index c4c6aba48677..7ea36fc785c6 100644 --- a/core/libs/prx/libc/include/specifics/linux/ElfTypes.hpp +++ b/core/libs/prx/libc/include/specifics/linux/ElfTypes.hpp @@ -3,6 +3,11 @@ #ifdef __linux__ #include +#ifdef ANYPS5_GUEST_LIFTED +#include +extern "C" int GuestCpuIterateImages_nid_no_patch(int (*callback)(dl_phdr_info*, std::size_t, void*), void* data); +#define dl_iterate_phdr GuestCpuIterateImages_nid_no_patch +#endif #else #include diff --git a/core/libs/prx/libc/src/exception/Unwind.cpp b/core/libs/prx/libc/src/exception/Unwind.cpp index 95c8d1abc262..be96af151cb5 100644 --- a/core/libs/prx/libc/src/exception/Unwind.cpp +++ b/core/libs/prx/libc/src/exception/Unwind.cpp @@ -4,6 +4,7 @@ #include "prx/libc/include/exceptions/Runtime.hpp" #include "prx/libc/include/specifics/linux/ElfTypes.hpp" #include "prx/libc/src/specifics/x86_64/RegisterContext.cpp" +#include "prx/libc/include/GuestCpu.hpp" #if defined(__linux__) #include @@ -17,7 +18,7 @@ #include #include #endif -#if defined(__linux__) && defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 35)) +#if defined(__linux__) && defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 35)) && !defined(ANYPS5_GUEST_LIFTED) #define LIBC_UNWIND_FIND_OBJECT 1 #include #include @@ -147,8 +148,18 @@ Word ResolveStub(Word personality) { } #endif +#ifdef ANYPS5_GUEST_LIFTED +static Word ResolveImport(Word address) { + const char* name = GuestCpuImportName_nid_no_patch(address); + if (name == nullptr) return address; + if (std::strcmp(name, "__gxx_personality_v0") == 0 || std::strcmp(name, "XwLA5cTHjt4") == 0) return reinterpret_cast(__gxx_personality_v0_nid_postfix); + if (std::strcmp(name, "__gcc_personality_v0") == 0 || std::strcmp(name, "FW2QeoYK7-c") == 0) return reinterpret_cast(__gcc_personality_v0_nid_postfix); + return address; +} +#endif + _Unwind_Reason_Code CallPersonality(Word personality, _Unwind_Action actions, _Unwind_Exception* exception, _Unwind_Context* context) { -#ifdef _WIN32 +#if defined(_WIN32) || defined(ANYPS5_GUEST_LIFTED) personality = ResolveImport(personality); #endif if (personality == reinterpret_cast(__gxx_personality_v0_nid_postfix) || personality == reinterpret_cast(__gxx_personality_v0)) @@ -699,7 +710,11 @@ _Unwind_Reason_Code PhaseTwo(_Unwind_Context context, _Unwind_Exception* excepti } else if (context.cfa == exception->private_2) actions = _Unwind_Action(actions | _UA_HANDLER_FRAME); if (frame.personality) { auto result = CallPersonality(frame.personality, actions, exception, &context); +#ifdef ANYPS5_GUEST_LIFTED + if (result == _URC_INSTALL_CONTEXT) GuestCpuUnwindResume_nid_no_patch(context.registers); +#else if (result == _URC_INSTALL_CONTEXT) LibcRestoreRegisters(context.registers); +#endif if (result != _URC_CONTINUE_UNWIND) return _URC_FATAL_PHASE2_ERROR; } } @@ -718,8 +733,12 @@ _Unwind_Reason_Code PhaseTwo(_Unwind_Context context, _Unwind_Exception* excepti extern "C" { _Unwind_Reason_Code APS5_VABI _Unwind_RaiseException_nid_postfix(_Unwind_Exception* exception) { _Unwind_Context start; +#ifdef ANYPS5_GUEST_LIFTED + GuestCpuUnwindStart_nid_no_patch(start.registers); +#else LibcCaptureRegisters(start.registers); if (!LibcUnwind::Step(start)) return _URC_FATAL_PHASE1_ERROR; +#endif auto context = start; exception->private_1 = 0; for (unsigned depth = 0; depth < 65536; ++depth) { @@ -739,7 +758,7 @@ _Unwind_Reason_Code APS5_VABI _Unwind_RaiseException_nid_postfix(_Unwind_Excepti } [[noreturn]] void APS5_VABI _Unwind_Resume_nid_postfix(_Unwind_Exception* exception) { -#if defined(__linux__) +#if defined(__linux__) && !defined(ANYPS5_GUEST_LIFTED) if (!LibcException::Native(exception->exception_class) && !exception->private_1) { using Resume = void (*)(_Unwind_Exception*); const auto resume = reinterpret_cast(dlsym(RTLD_NEXT, "_Unwind_Resume")); @@ -748,8 +767,12 @@ _Unwind_Reason_Code APS5_VABI _Unwind_RaiseException_nid_postfix(_Unwind_Excepti } #endif _Unwind_Context context; +#ifdef ANYPS5_GUEST_LIFTED + GuestCpuUnwindStart_nid_no_patch(context.registers); +#else LibcCaptureRegisters(context.registers); if (!LibcUnwind::Step(context)) std::abort(); +#endif LibcUnwind::PhaseTwo(context, exception); std::abort(); } @@ -789,8 +812,12 @@ _Unwind_Ptr APS5_VABI _Unwind_GetTextRelBase_nid_postfix(_Unwind_Context* contex _Unwind_Reason_Code APS5_VABI _Unwind_ForcedUnwind_nid_postfix(_Unwind_Exception* exception, _Unwind_Stop_Fn stop, void* argument) { if (!stop) return _URC_FATAL_PHASE2_ERROR; _Unwind_Context context; +#ifdef ANYPS5_GUEST_LIFTED + GuestCpuUnwindStart_nid_no_patch(context.registers); +#else LibcCaptureRegisters(context.registers); if (!LibcUnwind::Step(context)) return _URC_FATAL_PHASE2_ERROR; +#endif exception->private_1 = reinterpret_cast(stop); exception->private_2 = reinterpret_cast(argument); return LibcUnwind::PhaseTwo(context, exception); @@ -798,8 +825,12 @@ _Unwind_Reason_Code APS5_VABI _Unwind_ForcedUnwind_nid_postfix(_Unwind_Exception _Unwind_Reason_Code APS5_VABI _Unwind_Backtrace_nid_postfix(_Unwind_Trace_Fn trace, void* argument) { _Unwind_Context context; +#ifdef ANYPS5_GUEST_LIFTED + GuestCpuUnwindStart_nid_no_patch(context.registers); +#else LibcCaptureRegisters(context.registers); if (!LibcUnwind::Step(context)) return _URC_END_OF_STACK; +#endif for (unsigned depth = 0; depth < 65536; ++depth) { LibcUnwind::Frame frame; LibcUnwind::Rules rules; if (!LibcUnwind::GetRules(context, frame, rules)) return _URC_END_OF_STACK; From 5b76d214172b274fb6bd66268d7fe5feac77d4c4 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 20:48:37 +0000 Subject: [PATCH 19/31] feat(libc): iterate the lifted program's guest images in dl_iterate_phdr In an ANYPS5_GUEST_LIFTED build the guest images are not host shared objects, so glibc's dl_iterate_phdr cannot see them: the application heap never found the executable's process parameters. Every caller now includes ElfTypes.hpp instead of , which in a lifted build lists the program's guest images through GuestCpuIterateImages. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp | 6 +++--- core/libs/prx/libc/src/GuestAllocations.cpp | 4 ---- core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp | 2 +- core/libs/prx/libkernel/Module/src/ModuleInfo.cpp | 4 +--- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index d408bc0e5ccb..6a68ab83298c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -51,14 +51,14 @@ #include #include #include -#ifndef __APPLE__ -#include -#endif #include #include #include #include #include +#ifndef __APPLE__ +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" +#endif #include "prx/libc/include/specifics/linux/HostMapping.hpp" #ifdef __APPLE__ #include diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index a7fb66830e2c..cce4e2c632f9 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -26,13 +26,9 @@ #ifdef __APPLE__ #include #include -#else -#include #endif #include #include -#endif -#ifdef __APPLE__ #include "prx/libc/include/specifics/linux/ElfTypes.hpp" #endif diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index c562b3872d0c..0b4c50314203 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -25,9 +25,9 @@ #if defined(__linux__) #include -#include #include #include +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" #include #include #include diff --git a/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp b/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp index b9b95408ff65..92eed2500258 100644 --- a/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp +++ b/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp @@ -27,13 +27,11 @@ #include #include #include -#include "prx/libc/include/specifics/linux/ElfTypes.hpp" -#else -#include #endif #include #include #include +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" #endif extern "C" std::int32_t ModuleIdForImage_nid_no_patch(const void* native); From 41c7aed3982a51af0d5e092a9a117643e9f4125b Mon Sep 17 00:00:00 2001 From: oberonlift Date: Thu, 29 Oct 2026 23:14:09 +0000 Subject: [PATCH 20/31] fix(libc): keep the host's exception runtime in lifted builds A lifted build keeps the host's C++ runtime, but with libstdc++ the libc still defined std::current_exception, std::rethrow_exception and exception_ptr's members over its own runtime. Host code that caught an exception then got a null exception_ptr from the runtime that had not thrown it: the AGC driver reported a failed compute dispatch as "null asynchronous failure" and terminated. Lifted builds now give the guest its own exception pointer type, as libc++ builds do, and leave the host's exception pointers to the host runtime. The guest's _Result_base destructor export releases the guest pointer in place. Lifted builds also stop exporting the host ABI's names: Exports.cpp's __cxa_* and _Unwind_* aliases and Personality.cpp's global __gxx_personality_v0. A lifted executable loads libc before libstdc++ and libgcc_s, so the host's own throws bound to libc's versions and aborted. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/CMakeLists.txt | 9 +++++++ core/libs/prx/libc/include/Api.hpp | 2 +- .../libc/src/exception/ExceptionPointer.cpp | 8 ++++++- core/libs/prx/libc/src/exception/Exports.cpp | 2 ++ .../prx/libc/src/exception/Personality.cpp | 2 +- core/libs/tests/HostExceptionPointer.cpp | 24 +++++++++++++++++++ docs/dev/debt/libc.md | 2 ++ 7 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 core/libs/tests/HostExceptionPointer.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 156a2b9ba0d6..98bd9e0f7b7b 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -1609,6 +1609,15 @@ if(BUILD_TESTING) "#1 thread [0-9]+ libTraced.prx Integers from 0x00000000004[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f] guest_call_trace_fixed_base_tests\\+0x[0-9a-f]?[0-9a-f]?[0-9a-f]?[0-9a-f]?[0-9a-f][^0-9a-f]") endif() endif() + add_test_executable(host_exception_pointer_tests ${LIBS_INCLUDE_DIR}/tests/HostExceptionPointer.cpp) + target_include_directories(host_exception_pointer_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(host_exception_pointer_tests PRIVATE libc) + configure_windows_unwind(host_exception_pointer_tests) + add_test(NAME host_exception_pointer COMMAND host_exception_pointer_tests) + if(WIN32) + set_tests_properties(host_exception_pointer PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_syslog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSyslog.cpp) target_include_directories(guest_syslog_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_syslog_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index a2e337db425a..704292501632 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -15,7 +15,7 @@ #include #include -#ifdef _LIBCPP_VERSION +#if defined(_LIBCPP_VERSION) || defined(ANYPS5_GUEST_LIFTED) struct GuestExceptionPointer { void* object {}; explicit GuestExceptionPointer(void* primary) noexcept : object(primary) {} diff --git a/core/libs/prx/libc/src/exception/ExceptionPointer.cpp b/core/libs/prx/libc/src/exception/ExceptionPointer.cpp index 6b808a870c1e..7ab9d9efab3f 100644 --- a/core/libs/prx/libc/src/exception/ExceptionPointer.cpp +++ b/core/libs/prx/libc/src/exception/ExceptionPointer.cpp @@ -1,7 +1,7 @@ #include "prx/libc/include/Api.hpp" #include -#ifndef _LIBCPP_VERSION +#if !defined(_LIBCPP_VERSION) && !defined(ANYPS5_GUEST_LIFTED) #include extern "C" { @@ -138,6 +138,12 @@ GuestExceptionPointer APS5_VABI _ZSt17current_exceptionv_nid_postfix() noexcept LibcException::Terminate(); } + +#ifndef _LIBCPP_VERSION +void APS5_VABI _ZNSt13__future_base12_Result_baseD2Ev_nid_postfix(std::__future_base::_Result_base* self) noexcept { + _ZNSt15__exception_ptr13exception_ptr10_M_releaseEv_nid_postfix(reinterpret_cast(&self->_M_error)); +} +#endif } #endif diff --git a/core/libs/prx/libc/src/exception/Exports.cpp b/core/libs/prx/libc/src/exception/Exports.cpp index 231c5e06792e..6e4e4db24f35 100644 --- a/core/libs/prx/libc/src/exception/Exports.cpp +++ b/core/libs/prx/libc/src/exception/Exports.cpp @@ -13,6 +13,7 @@ extern "C" [[noreturn]] GCC_HIDDEN_FN void APS5_VABI LibcRuntimeAssertion(const char*, int, const char*, const char*) { std::abort(); } GCC_LOCAL_ALIAS(_ZSt21__glibcxx_assert_failPKciS0_S0_, LibcRuntimeAssertion); +#ifndef ANYPS5_GUEST_LIFTED GCC_GLOBAL_ALIAS(__cxa_free_exception, __cxa_free_exception_nid_postfix); GCC_GLOBAL_ALIAS(_ZSt9terminatev, _ZSt9terminatev_nid_postfix); GCC_GLOBAL_ALIAS(__cxa_call_terminate, __cxa_call_terminate_nid_postfix); @@ -32,6 +33,7 @@ GCC_GLOBAL_ALIAS(_Unwind_Resume, _Unwind_Resume_nid_postfix); GCC_GLOBAL_ALIAS(_Unwind_RaiseException, _Unwind_RaiseException_nid_postfix); GCC_GLOBAL_ALIAS(_Unwind_Resume_or_Rethrow, _Unwind_Resume_or_Rethrow_nid_postfix); GCC_GLOBAL_ALIAS(_Unwind_DeleteException, _Unwind_DeleteException_nid_postfix); +#endif #ifdef _WIN32 extern "C" { diff --git a/core/libs/prx/libc/src/exception/Personality.cpp b/core/libs/prx/libc/src/exception/Personality.cpp index d4ed0a681f2b..39d5bca51d70 100644 --- a/core/libs/prx/libc/src/exception/Personality.cpp +++ b/core/libs/prx/libc/src/exception/Personality.cpp @@ -102,7 +102,7 @@ extern "C" _Unwind_Reason_Code APS5_VABI __gcc_personality_v0_nid_postfix( return _URC_CONTINUE_UNWIND; } -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(ANYPS5_GUEST_LIFTED) extern "C" _Unwind_Reason_Code __gxx_personality_v0( int version, _Unwind_Action actions, std::uint64_t exceptionClass, _Unwind_Exception* exception, _Unwind_Context* context diff --git a/core/libs/tests/HostExceptionPointer.cpp b/core/libs/tests/HostExceptionPointer.cpp new file mode 100644 index 000000000000..c7ffc71daf19 --- /dev/null +++ b/core/libs/tests/HostExceptionPointer.cpp @@ -0,0 +1,24 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + std::exception_ptr caught; + try { + throw std::runtime_error("host failure"); + } catch (...) { + caught = std::current_exception(); + } + Require(caught != nullptr); + try { + std::rethrow_exception(caught); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()) == "host failure"); + return 0; + } + return 1; +} diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 2fff409946ae..e3843354c725 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -245,6 +245,8 @@ - The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked - The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) cannot run arbitrary Windows exception or termination handlers. It accepts GCC C and C++ handlers identified by a project's `.sehgcc` section and interprets their LSDA with the guest personalities. A handler in an unmarked module, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), still stops unwinding and the exception is reported as unhandled. SjLj exceptions are unsupported; Scoop GCC 15.2.0 selects them when SEH is disabled and lacks the System V ABI SEH compiler fix carried by MSYS2. - A guest frame built with `-fno-asynchronous-unwind-tables`, and libsystem_pthread `thread_start`, still fail to unwind on macOS. Compact unwind records them as encoding 0, and the [unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) rejects that encoding instead of inventing a CFA. A DWARF-mode entry whose `__eh_frame` FDE is missing or rejected fails the same way, with no fallback frame. libc itself is built with `-femit-dwarf-unwind=always` and `-Wl,-no_compact_unwind` (#8311), so its own frames stay in DWARF. The unwinder walks `__TEXT,__unwind_info` when `__eh_frame` misses the frame. A personality index in that section is a signed 32-bit delta from the Mach-O image base. +- In a lifted build the libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) runs a guest frame's personality routine only when it is AnyPS5's `__gxx_personality_v0` or `__gcc_personality_v0`, imported by name or NID. A personality routine in guest code is never called: the unwind stops with a fatal phase error, and the exception terminates the program. +- In a lifted build the libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) does not unwind through native frames: a guest exception thrown in a guest callback that native code called, such as a `qsort` comparison function, ends at the callback's return into native code and terminates the program as unhandled, even when a guest frame above the native one would catch it. - The libc [SSE4a trap emulation](../../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. - [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists host interfaces and IPv4/IPv6 addresses in FreeBSD's layouts. On Windows, `ifi_collisions` is 0, `ifi_baudrate` is the transmit link speed and the multicast counters include broadcasts; its flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST` (PPP/tunnel adapters omit broadcast and point-to-point, and an unspecified prefix length gets a host mask). On Linux, macOS and Windows, `AF_LINK` maps non-Ethernet/loopback/PPP types to `IFT_OTHER` (1); Linux's `if_data` leaves `ifi_omcasts`, `ifi_noproto`, `ifi_hwassist`, `ifi_metric`, `ifi_epoch`, `ifi_lastchange`, unknown baud rates and address-entry `ifa_data` as 0/null, and macOS's 32-bit host counters wrap and `ifi_oqdrops`, `ifi_hwassist`, `ifi_epoch` and `ifi_lastchange` are 0; a point-to-point address without a peer loses `IFF_POINTOPOINT`, and an omitted IPv4 broadcast is derived from the netmask. Interface names are the host's (`if` on Windows above 15 bytes). - [gethostname](../../../core/libs/prx/libc/src/Hostname.cpp) (libc) reports an empty name without querying the host machine name, as a payload process read on a PS5 (firmware 13.60): 0 with only the terminator for a 256-byte and a 1-byte buffer. Buffer copying, null queries and `ENAMETOOLONG` follow [FreeBSD gethostname](https://github.com/freebsd/freebsd-src/blob/main/lib/libc/gen/gethostname.c); whether a title process or a console with a configured name reads another name, and the integration with `sysctl` and name resolution, are unverified and not implemented. From 7d407b0a3d0bee2054012986fbfe081d10b8b7c7 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 08:04:04 +0000 Subject: [PATCH 21/31] feat(libc): read guest va_lists portably in lifted builds In a lifted build every VaList is a SysV va_list built by the guest or by a bridge, so the libc formats and scans through FormatArguments and its in-process scanner instead of the host's vprintf and vsscanf. The libc's own variadic functions wrap the host va_list in a tagged VaList through the shared APS5_VA_BEGIN macro, which the formatting and scanning tests now use as well. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestVaList.hpp | 33 ++++ .../prx/libc/include/WindowsFormatting.hpp | 36 ++++ core/libs/prx/libc/src/AllocatingStrings.cpp | 29 +-- core/libs/prx/libc/src/Formatting.cpp | 179 +++++++++++------- core/libs/prx/libc/src/FormattingWide.cpp | 8 - core/libs/prx/libc/src/ScanningWide.cpp | 8 - core/libs/prx/libc/src/Syslog.cpp | 15 +- core/libs/tests/Formatting.cpp | 38 ++-- core/libs/tests/GuestAllocatingStrings.cpp | 51 ++--- core/libs/tests/GuestStdioVariadic.cpp | 15 +- core/libs/tests/GuestWideFormatting.cpp | 15 +- core/libs/tests/GuestWideStreamScan.cpp | 9 +- 12 files changed, 243 insertions(+), 193 deletions(-) create mode 100644 core/libs/prx/libc/include/GuestVaList.hpp diff --git a/core/libs/prx/libc/include/GuestVaList.hpp b/core/libs/prx/libc/include/GuestVaList.hpp new file mode 100644 index 000000000000..2c4ee46daaa1 --- /dev/null +++ b/core/libs/prx/libc/include/GuestVaList.hpp @@ -0,0 +1,33 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTVALIST_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTVALIST_HPP + +#include "SceTypes.hpp" +#include +#include + +namespace LibcDetail { + +constexpr std::uint32_t HostVaListTag = 0xffffffffu; + +inline VaList HostVaList(std::va_list* host) { + VaList list{}; + list.gp_offset = HostVaListTag; + list.fp_offset = HostVaListTag; + list.reg_save_area = host; + return list; +} + +} + +#if defined(_WIN32) +#define APS5_VA_BEGIN(last) __builtin_sysv_va_list args; __builtin_sysv_va_start(args, last) +#define APS5_VA_END() __builtin_sysv_va_end(args) +#elif defined(ANYPS5_GUEST_LIFTED) +#define APS5_VA_BEGIN(last) std::va_list hostArgs; va_start(hostArgs, last); VaList args[1] = {LibcDetail::HostVaList(&hostArgs)} +#define APS5_VA_END() va_end(hostArgs) +#else +#define APS5_VA_BEGIN(last) std::va_list args; va_start(args, last) +#define APS5_VA_END() va_end(args) +#endif + +#endif diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index 849b3faddde5..c91d3f774c90 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -3,10 +3,12 @@ #include "SceTypes.hpp" #include "prx/libc/include/GuestLongDouble.hpp" +#include "prx/libc/include/GuestVaList.hpp" #include #include #include #include +#include #include #include #include @@ -34,12 +36,29 @@ class FormatArguments { VaList args; std::vector> numbered; size_t position = 1; +#ifdef ANYPS5_GUEST_LIFTED + std::va_list host; + bool hosted = false; +#endif public: explicit FormatArguments(const void* source) { std::memcpy(&args, source, sizeof(args)); +#ifdef ANYPS5_GUEST_LIFTED + hosted = args.gp_offset == HostVaListTag; + if (hosted) va_copy(host, *static_cast(args.reg_save_area)); +#endif + } + +#ifdef ANYPS5_GUEST_LIFTED + ~FormatArguments() { + if (hosted) va_end(host); } + FormatArguments(const FormatArguments&) = delete; + FormatArguments& operator=(const FormatArguments&) = delete; +#endif + void Number(const std::vector& classes) { numbered.resize(classes.size()); for (size_t index = 0; index < classes.size(); ++index) { @@ -64,7 +83,24 @@ class FormatArguments { } private: +#ifdef ANYPS5_GUEST_LIFTED + template T FetchHost() { + if constexpr (std::is_same_v) { + return va_arg(host, long double); + } else if constexpr (std::is_floating_point_v) { + return static_cast(va_arg(host, double)); + } else if constexpr (std::is_integral_v && sizeof(T) < sizeof(int)) { + return static_cast(va_arg(host, int)); + } else { + return va_arg(host, T); + } + } +#endif + template T Fetch() { +#ifdef ANYPS5_GUEST_LIFTED + if (hosted) return FetchHost(); +#endif const void* address; if constexpr (std::is_same_v) { if (args.fp_offset < 176) { diff --git a/core/libs/prx/libc/src/AllocatingStrings.cpp b/core/libs/prx/libc/src/AllocatingStrings.cpp index 672b2269c2e6..1d51648844db 100644 --- a/core/libs/prx/libc/src/AllocatingStrings.cpp +++ b/core/libs/prx/libc/src/AllocatingStrings.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/GuestVaList.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" @@ -51,19 +52,9 @@ char* APS5_VABI strdup_nid_postfix(const char* source) { } int APS5_VABI asprintf_nid_postfix(char** destination, const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vasprintf_nid_postfix(destination, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } @@ -112,19 +103,9 @@ int APS5_VABI vdprintf_nid_postfix(int descriptor, const char* format, VaList* a } int APS5_VABI dprintf_nid_postfix(int descriptor, const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vdprintf_nid_postfix(descriptor, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 6b7c06f01e21..826531c634c0 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -39,6 +39,10 @@ namespace { #ifndef _WIN32 bool RequiresGuestFormatting(const char* format) { +#ifdef ANYPS5_GUEST_LIFTED + static_cast(format); + return true; +#else if (format == nullptr) return false; while ((format = std::strchr(format, '%')) != nullptr) { ++format; @@ -55,6 +59,7 @@ bool RequiresGuestFormatting(const char* format) { if (*format) ++format; } return false; +#endif } std::size_t HostFormatSize(std::size_t size) { @@ -123,7 +128,19 @@ bool ScansInProcess(const char* format) { LibcDetail::RequireNoPositionalWideScan(format); return true; } +#ifdef ANYPS5_GUEST_LIFTED + return true; +#else return HasNarrowCharacterScan(format); +#endif +} + +bool ScansStreamInProcess(const char* format) { +#ifdef ANYPS5_GUEST_LIFTED + return ScansInProcess(format); +#else + return ScansWideInProcess(format); +#endif } const char* CutAtQuadScanDirective(const char* format, std::string& head) { @@ -585,7 +602,7 @@ int APS5_VABI vasprintf_nid_postfix(char** destination, const char* format, VaLi *destination = nullptr; if (!format || !args) { errno = 22; return -1; } try { -#ifdef _WIN32 +#if defined(_WIN32) || defined(ANYPS5_GUEST_LIFTED) std::string text; const int count = LibcDetail::FormatWindows(nullptr, 0, format, args, &text); const char* source = text.c_str(); @@ -643,22 +660,20 @@ int APS5_VABI vfprintf_nid_postfix(FileStream* stream, const char* format, VaLis } int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vfprintf_nid_postfix(stream, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } +#ifdef ANYPS5_GUEST_LIFTED +int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vfscanf_nid_postfix(stream, format, args); + APS5_VA_END(); + return result; +} +#else int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...) { #ifndef _WIN32 if (HasNarrowCharacterScan(format) && LibcDetail::FindQuadScanDirective(format) == nullptr) { @@ -690,8 +705,39 @@ int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...) { stream->KeepDirectoryReadError(result == -1); return result; } +#endif -#ifdef _WIN32 +#if defined(ANYPS5_GUEST_LIFTED) + +int APS5_VABI printf_nid_postfix(const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vprintf_nid_postfix(format, args); + APS5_VA_END(); + return result; +} + +int APS5_VABI libc_printf_nid_postfix(const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vprintf_nid_postfix(format, args); + APS5_VA_END(); + return result; +} + +int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vsnprintf_nid_postfix(buffer, size, format, args); + APS5_VA_END(); + return result; +} + +int APS5_VABI sprintf_nid_postfix(char* buffer, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vsprintf_nid_postfix(buffer, format, args); + APS5_VA_END(); + return result; +} + +#elif defined(_WIN32) int APS5_VABI printf_nid_postfix(const char* format, ...) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } @@ -787,12 +833,11 @@ int APS5_VABI vsscanf_nid_postfix(const char* input, const char* format, VaList* #endif } -#ifdef _WIN32 +#if defined(_WIN32) || defined(ANYPS5_GUEST_LIFTED) int APS5_VABI sscanf_nid_postfix(const char* input, const char* format, ...) { - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); + APS5_VA_BEGIN(format); const int result = vsscanf_nid_postfix(input, format, reinterpret_cast(args)); - __builtin_sysv_va_end(args); + APS5_VA_END(); return result; } #else @@ -808,7 +853,16 @@ int APS5_VABI sscanf_nid_postfix(const char* input, const char* format, ...) { } #endif -#ifdef _WIN32 +#if defined(ANYPS5_GUEST_LIFTED) + +int APS5_VABI sscanf_s_nid_postfix(const char* buffer, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vsscanf_s_nid_postfix(buffer, format, args); + APS5_VA_END(); + return result; +} + +#elif defined(_WIN32) int APS5_VABI sscanf_s_nid_postfix(const char* buffer, const char* format, ...) { __builtin_sysv_va_list args; @@ -916,19 +970,9 @@ int APS5_VABI vprintf_s_nid_postfix(const char* format, VaList* args) { } int APS5_VABI fprintf_s_nid_postfix(FileStream* stream, const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vfprintf_s_nid_postfix(stream, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } @@ -973,6 +1017,33 @@ int APS5_VABI vsprintf_s_nid_postfix(char* buffer, size_t size, const char* form return result; } +#ifdef ANYPS5_GUEST_LIFTED + +int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vsprintf_s_nid_postfix(buffer, size, format, args); + APS5_VA_END(); + return result; +} + +int APS5_VABI snprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = FormatSecure(buffer, size, format, args); + APS5_VA_END(); + return result; +} + +int APS5_VABI printf_s_nid_postfix(const char* format, ...) { + if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } + APS5_VA_BEGIN(format); + _Stdout_nid_postfix.Orientation(-1); + const int result = LibcDetail::PrintWindows(format, args); + APS5_VA_END(); + return result; +} + +#else + int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...) { std::va_list args; va_start(args, format); @@ -1001,6 +1072,8 @@ int APS5_VABI printf_s_nid_postfix(const char* format, ...) { #endif +#endif + int APS5_VABI puts_nid_postfix(const char* s) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(&_Stdout_nid_postfix); @@ -1024,7 +1097,7 @@ int APS5_VABI vfscanf_nid_postfix(FileStream* stream, const char* format, VaList stream->Orientation(-1); if (!stream->SwitchToReading()) return EOF; int result = 0; - if (ScansWideInProcess(format)) { + if (ScansStreamInProcess(format)) { LibcDetail::FormatArguments arguments(args); result = ScanSource(StreamSource(native), format, false, [&] { return arguments.Next(); }, [] { return 0u; }); } else { @@ -1047,19 +1120,9 @@ int APS5_VABI vscanf_nid_postfix(const char* format, VaList* args) { } int APS5_VABI scanf_nid_postfix(const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vfscanf_nid_postfix(__stdinp_nid_postfix, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } @@ -1073,36 +1136,16 @@ int APS5_VABI vscanf_s_nid_postfix(const char* format, VaList* args) { } int APS5_VABI fscanf_s_nid_postfix(FileStream* stream, const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vfscanf_s_nid_postfix(stream, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } int APS5_VABI scanf_s_nid_postfix(const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vfscanf_s_nid_postfix(__stdinp_nid_postfix, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index 158bd33878a2..e5a01391a836 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -17,14 +17,6 @@ #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/WindowsFormatting.hpp" -#ifdef _WIN32 -#define APS5_VA_BEGIN(last) __builtin_sysv_va_list args; __builtin_sysv_va_start(args, last) -#define APS5_VA_END() __builtin_sysv_va_end(args) -#else -#define APS5_VA_BEGIN(last) std::va_list args; va_start(args, last) -#define APS5_VA_END() va_end(args) -#endif - namespace { bool In(char16_t character, const char* set) { diff --git a/core/libs/prx/libc/src/ScanningWide.cpp b/core/libs/prx/libc/src/ScanningWide.cpp index 737fcf5585c6..3f947a33eb70 100644 --- a/core/libs/prx/libc/src/ScanningWide.cpp +++ b/core/libs/prx/libc/src/ScanningWide.cpp @@ -17,14 +17,6 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/WindowsFormatting.hpp" -#ifdef _WIN32 -#define APS5_VA_BEGIN(last) __builtin_sysv_va_list args; __builtin_sysv_va_start(args, last) -#define APS5_VA_END() __builtin_sysv_va_end(args) -#else -#define APS5_VA_BEGIN(last) std::va_list args; va_start(args, last) -#define APS5_VA_END() va_end(args) -#endif - namespace { enum class Fit { Invalid, Prefix, Valid }; diff --git a/core/libs/prx/libc/src/Syslog.cpp b/core/libs/prx/libc/src/Syslog.cpp index 3c3039462d06..deb5174df476 100644 --- a/core/libs/prx/libc/src/Syslog.cpp +++ b/core/libs/prx/libc/src/Syslog.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/GuestVaList.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -135,19 +136,9 @@ void APS5_VABI vsyslog_nid_postfix(int priority, const char* format, VaList* arg } void APS5_VABI syslog_nid_postfix(int priority, const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); vsyslog_nid_postfix(priority, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); } } diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index e2af7ccd32cd..3d4b1c791780 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/GuestVaList.hpp" #include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/ApplicationHeap.hpp" @@ -18,55 +19,45 @@ #include #include -#if defined(__clang__) && !defined(_WIN32) -#define __builtin_sysv_va_list va_list -#define __builtin_sysv_va_start va_start -#define __builtin_sysv_va_end va_end -#endif - static void Require(bool condition) { if (!condition) throw std::runtime_error("Formatting check failed"); } static int APS5_VABI FormatList(char* buffer, size_t size, const char* format, ...) { - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); + APS5_VA_BEGIN(format); VaList list; std::memcpy(&list, args, sizeof(list)); const VaList original = list; const int result = vsnprintf_nid_postfix(buffer, size, format, &list); Require(std::memcmp(&list, &original, sizeof(list)) == 0); - __builtin_sysv_va_end(args); + APS5_VA_END(); return result; } static int APS5_VABI FormatListS(char* buffer, size_t size, const char* format, ...) { - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); + APS5_VA_BEGIN(format); VaList list; std::memcpy(&list, args, sizeof(list)); const int result = vsnprintf_s_nid_postfix(buffer, size, format, &list); - __builtin_sysv_va_end(args); + APS5_VA_END(); return result; } static int APS5_VABI ScanList(const char* buffer, const char* format, ...) { - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); + APS5_VA_BEGIN(format); VaList list; std::memcpy(&list, args, sizeof(list)); const int result = vsscanf_nid_postfix(buffer, format, &list); - __builtin_sysv_va_end(args); + APS5_VA_END(); return result; } static int APS5_VABI ScanListS(const char* buffer, const char* format, ...) { - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); + APS5_VA_BEGIN(format); VaList list; std::memcpy(&list, args, sizeof(list)); const int result = vsscanf_s_nid_postfix(buffer, format, &list); - __builtin_sysv_va_end(args); + APS5_VA_END(); return result; } @@ -106,12 +97,11 @@ static void CheckCharacterScanLengths() { } static int APS5_VABI PrintList(const char* format, ...) { - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); + APS5_VA_BEGIN(format); VaList list; std::memcpy(&list, args, sizeof(list)); const int result = vprintf_nid_postfix(format, &list); - __builtin_sysv_va_end(args); + APS5_VA_END(); return result; } @@ -689,6 +679,12 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { char narrow[8] = {}; Require(sscanf_nid_postfix("ab%", "%7[^%ls]", narrow) == 1 && std::strcmp(narrow, "ab") == 0); #endif +#ifdef ANYPS5_GUEST_LIFTED + { + int untouched = 7; + Require(sscanf_nid_postfix("5", "%1$d", &untouched) == 0 && untouched == 7); + } +#endif #ifdef _WIN32 char freebsd[64]; Require(snprintf_nid_postfix(freebsd, sizeof(freebsd), "%qd|%qu|%qx|%qo") == 15 && diff --git a/core/libs/tests/GuestAllocatingStrings.cpp b/core/libs/tests/GuestAllocatingStrings.cpp index 795209a327e8..f032e97dfa67 100644 --- a/core/libs/tests/GuestAllocatingStrings.cpp +++ b/core/libs/tests/GuestAllocatingStrings.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/GuestVaList.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" @@ -15,6 +16,7 @@ #include #else #include +#include #include #endif @@ -62,36 +64,33 @@ void InitializeHeap() { } static int APS5_VABI CallVasprintf(char** destination, const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vasprintf_nid_postfix(destination, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } -static int APS5_VABI CallVdprintf(int descriptor, const char* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); +#ifndef _WIN32 +void CheckVaListAtPageEnd() { + const auto page = static_cast(sysconf(_SC_PAGESIZE)); + void* mapping = mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(mapping != MAP_FAILED); + auto* pages = static_cast(mapping); + Require(mprotect(pages + page, page, PROT_NONE) == 0); + auto* list = reinterpret_cast(pages + page - sizeof(VaList)); + *list = VaList{48, 176, pages, pages}; + char* allocated = nullptr; + Require(vasprintf_nid_postfix(&allocated, "plain", list) == 5); + Require(allocated != nullptr && std::strcmp(allocated, "plain") == 0); + free_nid_postfix(allocated); + Require(munmap(mapping, page * 2) == 0); +} #endif + +static int APS5_VABI CallVdprintf(int descriptor, const char* format, ...) { + APS5_VA_BEGIN(format); const int result = vdprintf_nid_postfix(descriptor, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } @@ -144,6 +143,10 @@ int main() { Require(std::strcmp(allocated, "value=42 ok") == 0); free_nid_postfix(allocated); +#ifndef _WIN32 + CheckVaListAtPageEnd(); +#endif + *__error_nid_postfix() = 0; Require(dprintf_nid_postfix(-1, "test") == -1); Require(*__error_nid_postfix() == 9); diff --git a/core/libs/tests/GuestStdioVariadic.cpp b/core/libs/tests/GuestStdioVariadic.cpp index 414a9b1533e9..4078ba14765b 100644 --- a/core/libs/tests/GuestStdioVariadic.cpp +++ b/core/libs/tests/GuestStdioVariadic.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/GuestVaList.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -15,14 +16,6 @@ extern FileStream* __stdinp_nid_postfix; } -#ifdef _WIN32 -#define APS5_VA_BEGIN(last) __builtin_sysv_va_list args; __builtin_sysv_va_start(args, last) -#define APS5_VA_END() __builtin_sysv_va_end(args) -#else -#define APS5_VA_BEGIN(last) std::va_list args; va_start(args, last) -#define APS5_VA_END() va_end(args) -#endif - static void Require(bool value, const char* what) { if (value) return; std::fprintf(stderr, "failed: %s\n", what); @@ -117,6 +110,7 @@ static int APS5_VABI ScanWideSized(const char16_t* input, const char16_t* format return result; } +#ifndef ANYPS5_GUEST_LIFTED static long APS5_VABI SumThroughCopy(int count, ...) { APS5_VA_BEGIN(count); unsigned char before[24]; @@ -136,11 +130,14 @@ static long APS5_VABI SumThroughCopy(int count, ...) { APS5_VA_END(); return sum; } +#endif static void CheckVacopy() { +#ifndef ANYPS5_GUEST_LIFTED long expected = 0; for (long index = 1; index <= 9; ++index) expected += index * 100 * index - index * 100; Require(SumThroughCopy(9, 100L, 200L, 300L, 400L, 500L, 600L, 700L, 800L, 900L) == expected, "_Vacopy copy reads every argument"); +#endif unsigned char overlap[32]; for (int index = 0; index < 32; ++index) overlap[index] = static_cast(index); _Vacopy_nid_postfix(overlap + 8, overlap); @@ -158,6 +155,8 @@ static std::string Contents(FileStream& stream) { } int main() { + Require(fwide_nid_postfix(&_Stdout_nid_postfix, 0) == 0, "stdout starts unoriented"); + Require(printf_s_nid_postfix("%s", "") == 0 && fwide_nid_postfix(&_Stdout_nid_postfix, 0) == -1, "printf_s orients stdout for bytes"); FileStream scanned(TemporaryFile::Open()); Require(std::fputs("42 answer 7", scanned.GetHandle()) >= 0, "seed the scanned stream"); std::rewind(scanned.GetHandle()); diff --git a/core/libs/tests/GuestWideFormatting.cpp b/core/libs/tests/GuestWideFormatting.cpp index 18e61f72c1cf..18ed256557d5 100644 --- a/core/libs/tests/GuestWideFormatting.cpp +++ b/core/libs/tests/GuestWideFormatting.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/GuestVaList.hpp" #include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -13,19 +14,9 @@ #include static int APS5_VABI Format(char16_t* buffer, std::size_t size, const char16_t* format, ...) { -#ifdef _WIN32 - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); -#else - std::va_list args; - va_start(args, format); -#endif + APS5_VA_BEGIN(format); const int result = vswprintf_nid_postfix(buffer, size, format, reinterpret_cast(args)); -#ifdef _WIN32 - __builtin_sysv_va_end(args); -#else - va_end(args); -#endif + APS5_VA_END(); return result; } diff --git a/core/libs/tests/GuestWideStreamScan.cpp b/core/libs/tests/GuestWideStreamScan.cpp index fd0b373845f7..5f04594074b7 100644 --- a/core/libs/tests/GuestWideStreamScan.cpp +++ b/core/libs/tests/GuestWideStreamScan.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/GuestVaList.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -15,14 +16,6 @@ extern FileStream* __stdinp_nid_postfix; } -#ifdef _WIN32 -#define APS5_VA_BEGIN(last) __builtin_sysv_va_list args; __builtin_sysv_va_start(args, last) -#define APS5_VA_END() __builtin_sysv_va_end(args) -#else -#define APS5_VA_BEGIN(last) std::va_list args; va_start(args, last) -#define APS5_VA_END() va_end(args) -#endif - static void Require(bool value, const char* what) { if (value) return; std::fprintf(stderr, "failed: %s\n", what); From b38d5f525cc74d06ce133f0dad66844b451cbc7b Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 14:13:50 +0000 Subject: [PATCH 22/31] fix(libSceLibcInternal): start err and warn va_lists with APS5_VA_BEGIN Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceLibcInternal/Err.cpp | 43 +++++++----------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/core/libs/prx/libSceLibcInternal/Err.cpp b/core/libs/prx/libSceLibcInternal/Err.cpp index d89aa3560864..1ae2e3468b18 100644 --- a/core/libs/prx/libSceLibcInternal/Err.cpp +++ b/core/libs/prx/libSceLibcInternal/Err.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/GuestVaList.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include @@ -46,16 +47,6 @@ void Report(const char* format, VaList* args, const int* code) { } -#ifdef _WIN32 -#define APS5_ERR_VA_LIST __builtin_sysv_va_list -#define APS5_ERR_VA_START(list, last) __builtin_sysv_va_start(list, last) -#define APS5_ERR_VA_END(list) __builtin_sysv_va_end(list) -#else -#define APS5_ERR_VA_LIST std::va_list -#define APS5_ERR_VA_START(list, last) va_start(list, last) -#define APS5_ERR_VA_END(list) va_end(list) -#endif - extern "C" { void APS5_VABI err_set_file_nid_postfix(FileStream* stream) { @@ -95,63 +86,55 @@ void APS5_VABI vwarnx_nid_postfix(const char* format, VaList* args) { } void APS5_VABI warnc_nid_postfix(int code, const char* format, ...) { - APS5_ERR_VA_LIST args; - APS5_ERR_VA_START(args, format); + APS5_VA_BEGIN(format); vwarnc_nid_postfix(code, format, reinterpret_cast(args)); - APS5_ERR_VA_END(args); + APS5_VA_END(); } void APS5_VABI warn_nid_postfix(const char* format, ...) { const int code = *__error_nid_postfix(); - APS5_ERR_VA_LIST args; - APS5_ERR_VA_START(args, format); + APS5_VA_BEGIN(format); vwarnc_nid_postfix(code, format, reinterpret_cast(args)); - APS5_ERR_VA_END(args); + APS5_VA_END(); } void APS5_VABI _warn_nid_postfix(const char* format, ...) { - APS5_ERR_VA_LIST args; - APS5_ERR_VA_START(args, format); + APS5_VA_BEGIN(format); vwarn_nid_postfix(format, reinterpret_cast(args)); - APS5_ERR_VA_END(args); + APS5_VA_END(); } void APS5_VABI warnx_nid_postfix(const char* format, ...) { - APS5_ERR_VA_LIST args; - APS5_ERR_VA_START(args, format); + APS5_VA_BEGIN(format); vwarnx_nid_postfix(format, reinterpret_cast(args)); - APS5_ERR_VA_END(args); + APS5_VA_END(); } [[noreturn]] void APS5_VABI errc_nid_postfix(int status, int code, const char* format, ...) { - APS5_ERR_VA_LIST args; - APS5_ERR_VA_START(args, format); + APS5_VA_BEGIN(format); verrc_nid_postfix(status, code, format, reinterpret_cast(args)); } [[noreturn]] void APS5_VABI err_nid_postfix(int status, const char* format, ...) { const int code = *__error_nid_postfix(); - APS5_ERR_VA_LIST args; - APS5_ERR_VA_START(args, format); + APS5_VA_BEGIN(format); verrc_nid_postfix(status, code, format, reinterpret_cast(args)); } [[noreturn]] void APS5_VABI _err_nid_postfix(int status, const char* format, ...) { - APS5_ERR_VA_LIST args; - APS5_ERR_VA_START(args, format); + APS5_VA_BEGIN(format); verr_nid_postfix(status, format, reinterpret_cast(args)); } [[noreturn]] void APS5_VABI errx_nid_postfix(int status, const char* format, ...) { - APS5_ERR_VA_LIST args; - APS5_ERR_VA_START(args, format); + APS5_VA_BEGIN(format); verrx_nid_postfix(status, format, reinterpret_cast(args)); } From 44acfc8455b48ab25665c9b71e98e3766eee25ad Mon Sep 17 00:00:00 2001 From: oberonlift Date: Sat, 31 Oct 2026 18:59:27 +0000 Subject: [PATCH 23/31] fix(libSceAudio3d): hand report handlers a SysV va_list in lifted builds A report handler is guest code, which reads the call's arguments through the x86-64 SysV va_list in the report. The dispatcher copied its own host va_list there, which is that layout only on an x86-64 host, so a lifted build for aarch64 did not compile. In lifted builds the report now carries a SysV va_list built around the call's values: the named parameters fill every integer register, so the values are its overflow area, behind an empty register save area. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAudio3d/Export.cpp | 37 ++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAudio3d/Export.cpp b/core/libs/prx/libSceAudio3d/Export.cpp index 37d36a98b5e0..12832550efe5 100644 --- a/core/libs/prx/libSceAudio3d/Export.cpp +++ b/core/libs/prx/libSceAudio3d/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAudioOut/include/AudioOut.hpp" #include "prx/libSceAudio3d/include/Audio3d.hpp" #include +#include #include #include #include @@ -250,6 +251,28 @@ int DrainAssociatedAudioOuts() { } } +void DeliverReport(const Audio3dReport& report) { + Audio3dReportSlot slots[AUDIO3D_REPORT_SLOTS]; + { + std::lock_guard lock(g_report_mutex); + std::copy(std::begin(g_report_slots[report.type]), std::end(g_report_slots[report.type]), slots); + } + for (const Audio3dReportSlot& slot : slots) { + if (slot.handler != nullptr) slot.handler(&report, slot.user_data); + } +} + +#ifdef ANYPS5_GUEST_LIFTED +constexpr std::uint32_t SysVGeneralRegisterBytes = 6 * 8; +constexpr std::size_t SysVRegisterSaveBytes = 6 * 8 + 8 * 16; + +template +void DispatchReport(std::uint32_t type, std::uint32_t port_id, std::uint32_t object_id, int result, const char* function, const char* argument_types, std::array values) { + alignas(16) std::array registers{}; + const Audio3dReportArguments arguments{SysVGeneralRegisterBytes, SysVGeneralRegisterBytes, values.data(), registers.data()}; + DeliverReport({sizeof(Audio3dReport), port_id, object_id, type, result, function, argument_types, arguments}); +} +#else void APS5_VABI DispatchReport_nid_no_patch(std::uint32_t type, std::uint32_t port_id, std::uint32_t object_id, int result, const char* function, const char* argument_types, ...) { #ifdef _WIN32 __builtin_sysv_va_list arguments; @@ -261,20 +284,14 @@ void APS5_VABI DispatchReport_nid_no_patch(std::uint32_t type, std::uint32_t por static_assert(sizeof(arguments) == sizeof(Audio3dReportArguments)); Audio3dReport report{sizeof(Audio3dReport), port_id, object_id, type, result, function, argument_types, {}}; std::memcpy(&report.arguments, arguments, sizeof(report.arguments)); - Audio3dReportSlot slots[AUDIO3D_REPORT_SLOTS]; - { - std::lock_guard lock(g_report_mutex); - std::copy(std::begin(g_report_slots[type]), std::end(g_report_slots[type]), slots); - } - for (const Audio3dReportSlot& slot : slots) { - if (slot.handler != nullptr) slot.handler(&report, slot.user_data); - } + DeliverReport(report); #ifdef _WIN32 __builtin_sysv_va_end(arguments); #else va_end(arguments); #endif } +#endif template std::uint64_t ReportArgument(T value) { @@ -288,7 +305,11 @@ std::uint64_t ReportArgument(T value) { template void Report(std::uint32_t port_id, std::uint32_t object_id, int result, const char* function, const char* argument_types, Arguments... arguments) { if (g_call_report_handlers.load() == 0) return; +#ifdef ANYPS5_GUEST_LIFTED + DispatchReport(AUDIO3D_REPORT_TYPE_CALL, port_id, object_id, result, function, argument_types, std::array{ReportArgument(arguments)...}); +#else DispatchReport_nid_no_patch(AUDIO3D_REPORT_TYPE_CALL, port_id, object_id, result, function, argument_types, ReportArgument(arguments)...); +#endif } } From 281a69eb8b0e27bf5adf064bafeb6799ac07f473 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 4 Nov 2026 19:45:00 +0000 Subject: [PATCH 24/31] fix(libSceAudioPropagation): leave the host-side report out of lifted builds sceAudioPropagationReportApi hands its report handlers the call's own va_list, which is the x86-64 SysV one the guest handler reads only on an x86-64 host; a lifted build for aarch64 did not compile. In lifted builds a host caller now stops as not implemented. Guest calls already stop at the bridge, which has no free integer register and no va_list sibling to forward them through. The library's test leaves out its report checks, which read the report's va_list as the host's, in lifted builds and checks the rejection instead. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAudioPropagation/CMakeLists.txt | 3 +++ core/libs/prx/libSceAudioPropagation/Export.cpp | 8 ++++++++ core/libs/prx/libSceAudioPropagation/LiftedReport.cpp | 5 +++++ core/libs/tests/GuestAudioPropagation.cpp | 6 ++++++ docs/dev/debt/libSceAudioPropagation.md | 4 ++++ 5 files changed, 26 insertions(+) create mode 100644 core/libs/prx/libSceAudioPropagation/LiftedReport.cpp diff --git a/core/libs/prx/libSceAudioPropagation/CMakeLists.txt b/core/libs/prx/libSceAudioPropagation/CMakeLists.txt index a65abb2cd55c..55e64598ad8e 100644 --- a/core/libs/prx/libSceAudioPropagation/CMakeLists.txt +++ b/core/libs/prx/libSceAudioPropagation/CMakeLists.txt @@ -1,4 +1,7 @@ add_library(libSceAudioPropagation SHARED EXCLUDE_FROM_ALL Export.cpp InventoryExports.cpp) +if(ANYPS5_GUEST_LIFTED) + target_sources(libSceAudioPropagation PRIVATE LiftedReport.cpp) +endif() target_include_directories(libSceAudioPropagation PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceAudioPropagation) diff --git a/core/libs/prx/libSceAudioPropagation/Export.cpp b/core/libs/prx/libSceAudioPropagation/Export.cpp index 25ed8d81bc63..97761eac69cc 100644 --- a/core/libs/prx/libSceAudioPropagation/Export.cpp +++ b/core/libs/prx/libSceAudioPropagation/Export.cpp @@ -182,6 +182,10 @@ void requireNoPropagation(bool none, const char* function) { } +#ifdef ANYPS5_GUEST_LIFTED +[[noreturn]] void LiftedReportRejectHostArguments(); +#endif + extern "C" { std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const SystemOptions* options, AudioPropagationSystemMemory* outMemory) { @@ -515,6 +519,9 @@ std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle sy int APS5_VABI sceAudioPropagationReportApi(std::uint32_t category, AudioPropagationHandle handle, std::int32_t result, std::int32_t operation, const char* function, const char* format, ...) { require(category < 2, __func__); +#ifdef ANYPS5_GUEST_LIFTED + LiftedReportRejectHostArguments(); +#else #ifdef _WIN32 __builtin_sysv_va_list arguments; __builtin_sysv_va_start(arguments, format); @@ -565,6 +572,7 @@ int APS5_VABI sceAudioPropagationReportApi(std::uint32_t category, AudioPropagat va_end(arguments); #endif return 0; +#endif } int APS5_VABI sceAudioPropagationResetAttributes(Attribute* attributes, std::uint32_t count) { diff --git a/core/libs/prx/libSceAudioPropagation/LiftedReport.cpp b/core/libs/prx/libSceAudioPropagation/LiftedReport.cpp new file mode 100644 index 000000000000..546194030d2c --- /dev/null +++ b/core/libs/prx/libSceAudioPropagation/LiftedReport.cpp @@ -0,0 +1,5 @@ +#include "prx/libc/include/General.hpp" + +[[noreturn]] void LiftedReportRejectHostArguments() { + NotImplemented_nid_no_patch("sceAudioPropagationReportApi from host code in a lifted build, whose va_list is not the SysV one a report handler reads"); +} diff --git a/core/libs/tests/GuestAudioPropagation.cpp b/core/libs/tests/GuestAudioPropagation.cpp index 314dabee5a40..c2ec1b5ef49d 100644 --- a/core/libs/tests/GuestAudioPropagation.cpp +++ b/core/libs/tests/GuestAudioPropagation.cpp @@ -64,6 +64,7 @@ AudioPropagation::Attribute Attribute(const void* value, std::size_t size) { return attribute; } +#ifndef ANYPS5_GUEST_LIFTED struct ReportProbe { std::uint32_t calls = 0; bool fieldsMatch = false; @@ -165,6 +166,7 @@ void APS5_VABI BlockingReport(const AudioPropagation::ReportInfo*, void* userDat probe.entered.store(true, std::memory_order_release); while (!probe.release.load(std::memory_order_acquire)) std::this_thread::yield(); } +#endif } @@ -562,6 +564,7 @@ int main() { Require(sceAudioPropagationSystemLock(0) == AudioPropagation::ErrorInvalidHandle, "system lock invalid handle"); +#ifndef ANYPS5_GUEST_LIFTED ReportProbe probe{}; AudioPropagation::ReportRegistration* registration = nullptr; Require(sceAudioPropagationReportRegisterHandler(2, CaptureReport, &probe, ®istration) == -4, "invalid report category"); @@ -647,6 +650,9 @@ int main() { std::uint32_t unreturnedRegistrationCalls = 0; Require(sceAudioPropagationReportRegisterHandler(1, CountReport, &unreturnedRegistrationCalls, nullptr) == 0, "registration without output pointer failed"); Require(sceAudioPropagationReportApi(1, 0, 0, 0, "unreturned-registration", "") == 0 && unreturnedRegistrationCalls == 1, "registration without output pointer was not retained"); +#else + RequireThrows([] { sceAudioPropagationReportApi(0, 0, 0, 0, "lifted", ""); }, "a host report call in a lifted build was not rejected"); +#endif Require(Resolve("sceAudioPropagationSystemCreate") != nullptr && Resolve("sceAudioPropagationSourceSetAudioPath") != nullptr, "export lookup"); for (const char* name : {"sceAudioPropagationAudioPathInit", "sceAudioPropagationAudioPathPointInit", "sceAudioPropagationMaterialInit", "sceAudioPropagationPortalSettingsInit", "sceAudioPropagationRayInit", "sceAudioPropagationSourceRenderInfoInit", "sceAudioPropagationSourceSetAudioPathsParamInit", "sceAudioPropagationSystemMemoryInit", "sceAudioPropagationSystemOptionInit", "sceAudioPropagationSystem"}) { diff --git a/docs/dev/debt/libSceAudioPropagation.md b/docs/dev/debt/libSceAudioPropagation.md index af750c87c6d3..eafdd6219a6a 100644 --- a/docs/dev/debt/libSceAudioPropagation.md +++ b/docs/dev/debt/libSceAudioPropagation.md @@ -31,3 +31,7 @@ - [libSceAudioPropagation](../../../core/libs/prx/libSceAudioPropagation/Export.cpp): Native report registrations are the static slot addresses: category 0 uses `0x3a8d0` through `0x3a940` in `0x10`-byte steps; category 1 uses `0x3a950` through `0x3a9c0`. At `0x12990`, the module stores the callback and user data even when the output-registration pointer is null; it only omits writing the slot address. At `0x12ae0`, unregister compares the supplied address to each static slot and clears the matching slot. Consequently, after a slot is reused, an old registration pointer can unregister the new callback. The host preserves this native pointer-reuse behavior; its regression test and this limitation are intentional. - [`sceAudioPropagationSystemQueryInfo`](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) at `0x3220` returns `0x8a700002` (`ErrorInvalidHandle`) unless the output pointer is nonnull, the system resolves, and the size is exactly `0x30`; it writes `1` to output offset `0x18` (`parameter24`). `sceAudioPropagationSystemQueryMemory` at `0x2f10` (NID `7xyAxrusLko`) requires the options field at offset `0x18` to equal `6`; `SystemQueryInfo`'s `1` is a different output field and is not a valid query-memory input. No binary or disassembly is included in this repository. - [Initialization exports](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) ending in `Init`, plus `sceAudioPropagationSystem`, are not exported: the SDK declares the helpers `static inline` and none is in the SDK stub library or the supplied native module. Diagnostic layouts are recovered. The final system word and the fourth position/direction words have no confirmed semantics. + +### Functional + +- In a lifted build [sceAudioPropagationReportApi](../../../core/libs/prx/libSceAudioPropagation/Export.cpp) reports itself as not implemented. A report handler reads the call's arguments through the x86-64 SysV va_list in the report, which a host caller's varargs are not on aarch64. A guest call stops at its bridge: its six named arguments fill every integer register, and there is no va_list sibling that would take the guest's own list. From 39b1373cb8322e94cb014ed640b5e7f39743e0a2 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 07:12:37 +0000 Subject: [PATCH 25/31] feat(libc): convert guest long double at its boundaries Add GuestLongDouble to load and store the guest's x87 80-bit values, so the libc can compute with a host long double that is not x87 extended, such as aarch64's binary128, when built for a lifted guest. Formatting, scanning, strtold, modfl, _LDtest and the code that takes 80-bit values apart go through it; strtold parses with round-to-odd so that the final rounding is correct, and long double arguments that host callers pass through a va_list are rounded to the guest's precision. On an x87 host it only copies. Co-Authored-By: Claude Opus 5.5 --- .../libs/prx/libc/include/GuestLongDouble.hpp | 24 +++++++++++++ .../prx/libc/include/WindowsFormatting.hpp | 2 +- .../libs/prx/libc/src/DinkumwareInternals.cpp | 36 +++++++++++-------- core/libs/prx/libc/src/FloatBuiltins.cpp | 12 +++---- core/libs/prx/libc/src/Math.cpp | 28 +++++++-------- core/libs/prx/libc/src/MathExact.cpp | 20 +++++++---- core/libs/prx/libc/src/ScanningWide.cpp | 2 +- core/libs/prx/libc/src/Strings.cpp | 6 ++-- 8 files changed, 82 insertions(+), 48 deletions(-) diff --git a/core/libs/prx/libc/include/GuestLongDouble.hpp b/core/libs/prx/libc/include/GuestLongDouble.hpp index aac647771660..f6c312b3b4b1 100644 --- a/core/libs/prx/libc/include/GuestLongDouble.hpp +++ b/core/libs/prx/libc/include/GuestLongDouble.hpp @@ -1,10 +1,20 @@ #ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLONGDOUBLE_HPP #define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLONGDOUBLE_HPP +#include +#include +#include +#include +#include #include +#include #include #include +#if defined(ANYPS5_GUEST_LIFTED) && LDBL_MANT_DIG != 64 +extern "C" void GuestCpuStoreExtended_nid_no_patch(const long double* host, void* guest); +#endif + namespace LibcDetail { inline constexpr std::size_t GuestLongDoubleBytes = 16; @@ -34,6 +44,9 @@ inline void GuestLongDoubleStore(void* target, long double value) { if constexpr (std::numeric_limits::digits == 64) { std::memcpy(target, &value, sizeof(value)); } else { +#ifdef ANYPS5_GUEST_LIFTED + GuestCpuStoreExtended_nid_no_patch(&value, target); +#else std::uint64_t low; std::uint64_t high; std::memcpy(&low, &value, sizeof(low)); @@ -46,9 +59,20 @@ inline void GuestLongDoubleStore(void* target, long double value) { std::memcpy(bytes, &mantissa, sizeof(mantissa)); std::memcpy(bytes + 8, &signExponent, sizeof(signExponent)); std::memcpy(target, bytes, sizeof(bytes)); +#endif } } +inline std::array GuestLongDoubleImage(const long double value) { + std::array bytes{}; + GuestLongDoubleStore(bytes.data(), value); + return bytes; +} + +inline long double GuestLongDoubleParse(const char* text, char** end) { + return std::strtold(text, end); +} + } #endif diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index c91d3f774c90..b7232d2f81c1 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -86,7 +86,7 @@ class FormatArguments { #ifdef ANYPS5_GUEST_LIFTED template T FetchHost() { if constexpr (std::is_same_v) { - return va_arg(host, long double); + return LibcDetail::GuestLongDoubleRound(va_arg(host, long double)); } else if constexpr (std::is_floating_point_v) { return static_cast(va_arg(host, double)); } else if constexpr (std::is_integral_v && sizeof(T) < sizeof(int)) { diff --git a/core/libs/prx/libc/src/DinkumwareInternals.cpp b/core/libs/prx/libc/src/DinkumwareInternals.cpp index c7156b090285..518156c0cd25 100644 --- a/core/libs/prx/libc/src/DinkumwareInternals.cpp +++ b/core/libs/prx/libc/src/DinkumwareInternals.cpp @@ -1,5 +1,7 @@ #include "prx/libc/include/Api.hpp" +#include "prx/libc/include/GuestLongDouble.hpp" #include +#include #include #include #include @@ -40,9 +42,12 @@ int HexadecimalDigit(char value) { return -1; } +template +constexpr unsigned GuestDigits = sizeof(TFloat) <= 8 ? std::numeric_limits::digits : 64; + template TFloat ScaleHexadecimal(TFloat value, std::int64_t power) { - constexpr unsigned Precision = std::numeric_limits::digits; + constexpr unsigned Precision = GuestDigits; static_assert(Precision == 24 || Precision == 53 || Precision == 64); constexpr auto Hidden = std::uint64_t{1} << (Precision - 1); constexpr int MaximumExponent = 2 * std::numeric_limits::max_exponent - 1; @@ -57,10 +62,11 @@ TFloat ScaleHexadecimal(TFloat value, std::int64_t power) { mantissa = (bits & (Hidden - 1)) | Hidden; exponent = (bits >> (Precision - 1)) & MaximumExponent; } else { - static_assert(Precision == 64 && sizeof(TFloat) >= 10 && std::endian::native == std::endian::little); + static_assert(sizeof(TFloat) == sizeof(long double) && std::endian::native == std::endian::little); + const auto image = LibcDetail::GuestLongDoubleImage(value); std::uint16_t encoded; - std::memcpy(&mantissa, &value, sizeof(mantissa)); - std::memcpy(&encoded, reinterpret_cast(&value) + 8, sizeof(encoded)); + std::memcpy(&mantissa, image.data(), sizeof(mantissa)); + std::memcpy(&encoded, image.data() + 8, sizeof(encoded)); exponent = encoded & MaximumExponent; } auto adjusted = exponent + power; @@ -84,10 +90,11 @@ TFloat ScaleHexadecimal(TFloat value, std::int64_t power) { } else if constexpr (sizeof(TFloat) == 8) { return std::bit_cast((static_cast(adjusted) << (Precision - 1)) | (mantissa & (Hidden - 1))); } else { + std::array image{}; const auto encoded = static_cast(adjusted); - std::memcpy(&value, &mantissa, sizeof(mantissa)); - std::memcpy(reinterpret_cast(&value) + 8, &encoded, sizeof(encoded)); - return value; + std::memcpy(image.data(), &mantissa, sizeof(mantissa)); + std::memcpy(image.data() + 8, &encoded, sizeof(encoded)); + return LibcDetail::GuestLongDoubleLoad(image.data()); } } @@ -98,7 +105,7 @@ std::optional UnscaledHexadecimal(const char* text, char** end) { const bool negative = *cursor == '-'; if (*cursor == '+' || *cursor == '-') ++cursor; cursor += 2; - constexpr std::size_t Capacity = 7 * ((std::numeric_limits::digits + 27) / 28); + constexpr std::size_t Capacity = 7 * ((GuestDigits + 27) / 28); static_assert(Capacity * 4 < std::numeric_limits::max_exponent); constexpr auto CountLimit = std::numeric_limits::max() / 16; std::string digits; @@ -613,11 +620,11 @@ float APS5_VABI _FLog_nid_postfix(float x, int base) { } long double APS5_VABI _LLog_nid_postfix(long double x, int base) { - static_assert(sizeof(long double) == 16 && std::numeric_limits::digits == 64); + const auto image = LibcDetail::GuestLongDoubleImage(x); std::uint64_t mantissa; std::uint16_t signExponent; - std::memcpy(&mantissa, &x, sizeof(mantissa)); - std::memcpy(&signExponent, reinterpret_cast(&x) + sizeof(mantissa), sizeof(signExponent)); + std::memcpy(&mantissa, image.data(), sizeof(mantissa)); + std::memcpy(&signExponent, image.data() + sizeof(mantissa), sizeof(signExponent)); const int exponent = signExponent & 0x7fff; const bool negative = (signExponent & 0x8000) != 0; if (exponent == 0x7fff) { @@ -636,9 +643,10 @@ long double APS5_VABI _LLog_nid_postfix(long double x, int base) { int scale = std::max(exponent, 1) - shift - 0x3ffe; const std::uint64_t normalized = mantissa << shift; const std::uint16_t halfExponent = 0x3ffe; - long double fraction = 0; - std::memcpy(&fraction, &normalized, sizeof(normalized)); - std::memcpy(reinterpret_cast(&fraction) + sizeof(normalized), &halfExponent, sizeof(halfExponent)); + std::array fractionImage{}; + std::memcpy(fractionImage.data(), &normalized, sizeof(normalized)); + std::memcpy(fractionImage.data() + sizeof(normalized), &halfExponent, sizeof(halfExponent)); + long double fraction = LibcDetail::GuestLongDoubleLoad(fractionImage.data()); if (fraction < 0xb504f333f9de6484p-64L) { fraction = fraction + fraction; --scale; diff --git a/core/libs/prx/libc/src/FloatBuiltins.cpp b/core/libs/prx/libc/src/FloatBuiltins.cpp index 020d73eb1962..d698a0c0edac 100644 --- a/core/libs/prx/libc/src/FloatBuiltins.cpp +++ b/core/libs/prx/libc/src/FloatBuiltins.cpp @@ -7,6 +7,7 @@ #include #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestLongDouble.hpp" namespace { @@ -55,12 +56,11 @@ struct ExtendedBits { }; ExtendedBits DecomposeExtended(long double value) { - unsigned char bytes[sizeof(long double)]; - std::memcpy(bytes, &value, sizeof(bytes)); + const auto bytes = LibcDetail::GuestLongDoubleImage(value); std::uint64_t mantissa; std::uint16_t signExponent; - std::memcpy(&mantissa, bytes, sizeof(mantissa)); - std::memcpy(&signExponent, bytes + 8, sizeof(signExponent)); + std::memcpy(&mantissa, bytes.data(), sizeof(mantissa)); + std::memcpy(&signExponent, bytes.data() + 8, sizeof(signExponent)); return {mantissa, (signExponent & 0x8000u) != 0, static_cast(signExponent & 0x7fffu) - 16383}; } @@ -69,9 +69,7 @@ long double ComposeExtended(bool negative, int biased, std::uint64_t mantissa) { const std::uint16_t signExponent = static_cast((negative ? 0x8000u : 0u) | static_cast(biased)); std::memcpy(bytes, &mantissa, sizeof(mantissa)); std::memcpy(bytes + 8, &signExponent, sizeof(signExponent)); - long double value; - std::memcpy(&value, bytes, sizeof(value)); - return value; + return LibcDetail::GuestLongDoubleLoad(bytes); } template diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 8bad9a1a3ff3..7c80789c5b7b 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -177,6 +177,7 @@ #include #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestLongDouble.hpp" #include "prx/libc/include/GuestCpu.hpp" #include "prx/libc/include/SonyCrt.hpp" @@ -302,7 +303,7 @@ float AtanhfLog1p(float x) { return kFloat * Ln2High - ((halfSquare - (s * (halfSquare + r) + (kFloat * Ln2Low + c))) - f); } -static_assert(std::numeric_limits::digits == 64, "guest long double is the x87 80-bit format"); +static_assert(std::numeric_limits::digits >= 64, "host long double holds the guest x87 80-bit format"); struct ExtendedParts { std::uint64_t mantissa; @@ -327,11 +328,10 @@ TValue Hypot3(TValue x, TValue y, TValue z, THypot hypot) { } ExtendedParts SplitExtended(long double value) { - unsigned char bytes[sizeof(long double)]; - std::memcpy(bytes, &value, sizeof(value)); + const auto bytes = LibcDetail::GuestLongDoubleImage(value); ExtendedParts parts; - std::memcpy(&parts.mantissa, bytes, sizeof(parts.mantissa)); - std::memcpy(&parts.signExponent, bytes + sizeof(parts.mantissa), sizeof(parts.signExponent)); + std::memcpy(&parts.mantissa, bytes.data(), sizeof(parts.mantissa)); + std::memcpy(&parts.signExponent, bytes.data() + sizeof(parts.mantissa), sizeof(parts.signExponent)); return parts; } @@ -345,11 +345,9 @@ long double JoinExtended(std::uint64_t mantissa, std::uint16_t signExponent) { } long double ClearLowMantissaWord(long double value) { - unsigned char bytes[sizeof(long double)]; - std::memcpy(bytes, &value, sizeof(value)); - std::memset(bytes, 0, sizeof(std::uint32_t)); - std::memcpy(&value, bytes, sizeof(value)); - return value; + auto bytes = LibcDetail::GuestLongDoubleImage(value); + std::memset(bytes.data(), 0, sizeof(std::uint32_t)); + return LibcDetail::GuestLongDoubleLoad(bytes.data()); } long double SumAtCallerPrecision(long double high, long double low) { @@ -391,11 +389,11 @@ bool NanWithoutException(T value) { std::memcpy(&bits, &value, sizeof(bits)); return (bits & UINT64_C(0x7fffffffffffffff)) > UINT64_C(0x7ff0000000000000); } else { - static_assert(std::numeric_limits::digits == 64); + const auto bytes = LibcDetail::GuestLongDoubleImage(value); std::uint64_t significand; std::uint16_t signAndExponent; - std::memcpy(&significand, &value, sizeof(significand)); - std::memcpy(&signAndExponent, reinterpret_cast(&value) + sizeof(significand), sizeof(signAndExponent)); + std::memcpy(&significand, bytes.data(), sizeof(significand)); + std::memcpy(&signAndExponent, bytes.data() + sizeof(significand), sizeof(signAndExponent)); return (signAndExponent & 0x7fff) == 0x7fff && (significand & UINT64_C(0x7fffffffffffffff)) != 0; } } @@ -3803,9 +3801,9 @@ int APS5_VABI __signbitl_nid_postfix(long double x) { } int APS5_VABI __isfinitel_nid_postfix(long double x) { - static_assert(sizeof(long double) == 16 && std::numeric_limits::digits == 64 && std::numeric_limits::max_exponent == 16384 && std::endian::native == std::endian::little, "guest long double requires little-endian x87 extended precision storage"); + static_assert(sizeof(long double) == 16 && std::numeric_limits::max_exponent == 16384 && std::endian::native == std::endian::little, "guest long double requires little-endian storage with the x87 exponent range"); std::uint16_t exponent; - std::memcpy(&exponent, reinterpret_cast(&x) + 8, sizeof(exponent)); + std::memcpy(&exponent, LibcDetail::GuestLongDoubleImage(x).data() + 8, sizeof(exponent)); return (exponent & 0x7fffu) != 0x7fffu; } diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp index 4713849442d2..6c97a009aba0 100644 --- a/core/libs/prx/libc/src/MathExact.cpp +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -13,6 +13,7 @@ #include #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestLongDouble.hpp" namespace { @@ -32,15 +33,17 @@ struct ExtendedBits { std::uint16_t signExponent; }; -ExtendedBits Bits(const long double& value) { +ExtendedBits Bits(const void* bytes) { ExtendedBits bits; - unsigned char bytes[sizeof(long double)]; - std::memcpy(bytes, &value, sizeof(bytes)); std::memcpy(&bits.mantissa, bytes, sizeof(bits.mantissa)); - std::memcpy(&bits.signExponent, bytes + 8, sizeof(bits.signExponent)); + std::memcpy(&bits.signExponent, static_cast(bytes) + 8, sizeof(bits.signExponent)); return bits; } +ExtendedBits Bits(const long double& value) { + return Bits(LibcDetail::GuestLongDoubleImage(value).data()); +} + constexpr short Denormal = -2, Finite = -1, Zero = 0, Infinite = 1, NotANumber = 2; short TestExtended(const ExtendedBits& bits) { @@ -341,10 +344,13 @@ long double APS5_VABI sqrtl_nid_postfix(long double x) { long double APS5_VABI modfl_nid_postfix(long double x, long double* integral) { if (std::isnan(x)) { - *integral = x; + LibcDetail::GuestLongDoubleStore(integral, x); return x; } - return std::modf(x, integral); + long double whole; + const long double fraction = std::modf(x, &whole); + LibcDetail::GuestLongDoubleStore(integral, whole); + return fraction; } long double APS5_VABI nanl_nid_postfix(const char*) { return std::numeric_limits::quiet_NaN(); } @@ -447,7 +453,7 @@ int APS5_VABI __isnormall_nid_postfix(long double x) { return exponent != 0 && exponent != 0x7fff ? 1 : 0; } -short APS5_VABI _LDtest_nid_postfix(const long double* value) { return TestExtended(Bits(*value)); } +short APS5_VABI _LDtest_nid_postfix(const long double* value) { return TestExtended(Bits(static_cast(value))); } int APS5_VABI _Dclass_nid_postfix(double x) { return TestFloat(x); } int APS5_VABI _FDclass_nid_postfix(float x) { return TestFloat(x); } int APS5_VABI _LDclass_nid_postfix(long double x) { return TestExtended(Bits(x)); } diff --git a/core/libs/prx/libc/src/ScanningWide.cpp b/core/libs/prx/libc/src/ScanningWide.cpp index 3f947a33eb70..bb0183972085 100644 --- a/core/libs/prx/libc/src/ScanningWide.cpp +++ b/core/libs/prx/libc/src/ScanningWide.cpp @@ -369,7 +369,7 @@ class WideScanner { if (target != nullptr) { const int savedError = errno; if (directive.length == "L") { - LibcDetail::GuestLongDoubleStore(target, std::strtold(field.c_str(), nullptr)); + LibcDetail::GuestLongDoubleStore(target, LibcDetail::GuestLongDoubleParse(field.c_str(), nullptr)); } else if (directive.length == "l") { const double value = std::strtod(field.c_str(), nullptr); std::memcpy(target, &value, sizeof(value)); diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index c143dd201f52..a66ddf14147d 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -16,6 +16,7 @@ #include #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestLongDouble.hpp" #include "prx/libc/include/SonyCrt.hpp" namespace { @@ -657,7 +658,7 @@ float APS5_VABI strtof_nid_postfix(const char* str, char** endptr) { } long double APS5_VABI strtold_nid_postfix(const char* str, char** endptr) { static_assert(sizeof(long double) == 16, "Guest long double requires x87 extended precision storage"); - return ParseFloat(str, endptr, std::strtold); + return ParseFloat(str, endptr, LibcDetail::GuestLongDoubleParse); } int APS5_VABI atoi_nid_postfix(const char* str) { @@ -949,9 +950,8 @@ float APS5_VABI wcstof_nid_postfix(const char16_t* str, char16_t** endptr) { long double APS5_VABI wcstold_nid_postfix(const char16_t* str, char16_t** endptr) { #ifndef __EMSCRIPTEN__ static_assert(sizeof(long double) == 16); - static_assert(std::numeric_limits::digits == 64); #endif - return ParseAsciiPrefix(str, endptr, [](const char* text, char** end) { return ParseFloat(text, end, std::strtold); }); + return ParseAsciiPrefix(str, endptr, [](const char* text, char** end) { return ParseFloat(text, end, LibcDetail::GuestLongDoubleParse); }); } unsigned long long APS5_VABI wcstoul_nid_postfix(const char16_t* str, char16_t** endptr, int base) { From 9a4129d9f2a665f1f1954dcf2ee932beac6b4e56 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Sat, 31 Oct 2026 16:11:11 +0000 Subject: [PATCH 26/31] feat(libc): keep the guest's long double exactly in lifted builds A lifted libc held the guest's x87 long double in the host's long double: IEEE binary128 on Linux, which holds every 80-bit value but rounds each step differently, and a plain double on macOS, which holds neither. Lifted builds now keep it in GuestLongDouble, laid out as the guest stores it. Its arithmetic, square root, integer rounding and conversions to double, float and integers run through the ps5lift runtime's x87, so they round, honour precision control and raise exceptions as the guest's control and status words say. Comparisons, conversions from integers and doubles, frexp, scalbn, fmod, remainder, remquo, modf, logb and the fixed-direction roundings work on the bits. strtold and scanf("%Lf") parse with exact big-integer arithmetic, correctly rounded in the guest's rounding mode; the narrow scanner reads the field itself, with the wide scanner's FloatFit, now shared. printf already formatted the exact digits. On an x86-64 host with x87 behind the runtime's entry points, the parser matched glibc's x87 strtold in 58,244 cases across the four rounding modes, and the bit operations matched x87 libm in 5.6 million. The transcendental l functions and fmal still compute in the host's widest type and round the result, which on macOS keeps 53 bits; the debt entry says so. long double complex results are left out of lifted builds, where they were never bridged. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/CMakeLists.txt | 1 + core/libs/prx/libc/include/Api.hpp | 187 ++++--- .../libs/prx/libc/include/GuestLongDouble.hpp | 524 +++++++++++++++++- core/libs/prx/libc/include/ScanFloatField.hpp | 82 +++ .../prx/libc/include/WindowsFormatting.hpp | 38 +- .../libs/prx/libc/src/DinkumwareInternals.cpp | 62 +-- core/libs/prx/libc/src/FloatBuiltins.cpp | 42 +- core/libs/prx/libc/src/Formatting.cpp | 35 ++ core/libs/prx/libc/src/FormattingWide.cpp | 10 +- .../prx/libc/src/GuestLongDoubleParse.cpp | 264 +++++++++ core/libs/prx/libc/src/Math.cpp | 328 ++++++----- core/libs/prx/libc/src/MathExact.cpp | 149 ++--- core/libs/prx/libc/src/ScanningWide.cpp | 79 +-- core/libs/prx/libc/src/Strings.cpp | 16 +- core/libs/tests/GuestLongDouble.cpp | 3 + docs/dev/debt/libc.md | 3 + 16 files changed, 1358 insertions(+), 465 deletions(-) create mode 100644 core/libs/prx/libc/include/ScanFloatField.hpp create mode 100644 core/libs/prx/libc/src/GuestLongDoubleParse.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 98bd9e0f7b7b..65914a4aa815 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -143,6 +143,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/linux/DlIteratePhdr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/macos/GuestImages.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Strings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestLongDoubleParse.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/AllocatingStrings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/WideConversion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/BoundsChecking.cpp diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index 704292501632..09cd9689a763 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -1,6 +1,7 @@ #pragma once #include "prx/libc/include/specifics/linux/ElfTypes.hpp" +#include "prx/libc/include/GuestLongDouble.hpp" #include "prx/libc/include/specifics/itanium/UnwindAbi.hpp" #include "prx/libc/include/MallocManagedSize.hpp" #include "prx/libc/include/GuestTime.hpp" @@ -219,12 +220,12 @@ double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pt long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base); double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant); float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant); -long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant); +LibcDetail::GuestLongDouble APS5_VABI _LSin_nid_postfix(LibcDetail::GuestLongDouble x, unsigned int quadrant); double APS5_VABI _Log_nid_postfix(double x, int base); float APS5_VABI _FLog_nid_postfix(float x, int base); -long double APS5_VABI _LLog_nid_postfix(long double x, int base); -long double APS5_VABI _LDivide_nid_postfix(long double x, long double y); -long double APS5_VABI _LRecip_nid_postfix(long double x); +LibcDetail::GuestLongDouble APS5_VABI _LLog_nid_postfix(LibcDetail::GuestLongDouble x, int base); +LibcDetail::GuestLongDouble APS5_VABI _LDivide_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); +LibcDetail::GuestLongDouble APS5_VABI _LRecip_nid_postfix(LibcDetail::GuestLongDouble x); void* APS5_VABI opendir_nid_postfix(const char* path); GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void* handle); int APS5_VABI readdir_r_nid_postfix(void* handle, GuestDirectoryEntry* entry, GuestDirectoryEntry** result); @@ -342,34 +343,38 @@ Uint128 APS5_VABI __fixunssfti_nid_postfix(float a); std::uint32_t APS5_VABI __fixunsdfsi_nid_postfix(double a); std::uint64_t APS5_VABI __fixunsdfdi_nid_postfix(double a); Uint128 APS5_VABI __fixunsdfti_nid_postfix(double a); -std::int64_t APS5_VABI __fixxfdi_nid_postfix(long double a); -Int128 APS5_VABI __fixxfti_nid_postfix(long double a); -std::uint32_t APS5_VABI __fixunsxfsi_nid_postfix(long double a); -std::uint64_t APS5_VABI __fixunsxfdi_nid_postfix(long double a); -Uint128 APS5_VABI __fixunsxfti_nid_postfix(long double a); +std::int64_t APS5_VABI __fixxfdi_nid_postfix(LibcDetail::GuestLongDouble a); +Int128 APS5_VABI __fixxfti_nid_postfix(LibcDetail::GuestLongDouble a); +std::uint32_t APS5_VABI __fixunsxfsi_nid_postfix(LibcDetail::GuestLongDouble a); +std::uint64_t APS5_VABI __fixunsxfdi_nid_postfix(LibcDetail::GuestLongDouble a); +Uint128 APS5_VABI __fixunsxfti_nid_postfix(LibcDetail::GuestLongDouble a); double APS5_VABI __floatsidf_nid_postfix(std::int32_t a); double APS5_VABI __floatunsidf_nid_postfix(std::uint32_t a); float APS5_VABI __floatsisf_nid_postfix(std::int32_t a); float APS5_VABI __floatunsisf_nid_postfix(std::uint32_t a); double APS5_VABI __floatdidf_nid_postfix(std::int64_t a); float APS5_VABI __floatdisf_nid_postfix(std::int64_t a); -long double APS5_VABI __floatdixf_nid_postfix(std::int64_t a); +LibcDetail::GuestLongDouble APS5_VABI __floatdixf_nid_postfix(std::int64_t a); double APS5_VABI __floatundidf_nid_postfix(std::uint64_t a); float APS5_VABI __floatundisf_nid_postfix(std::uint64_t a); -long double APS5_VABI __floatundixf_nid_postfix(std::uint64_t a); +LibcDetail::GuestLongDouble APS5_VABI __floatundixf_nid_postfix(std::uint64_t a); double APS5_VABI __floattidf_nid_postfix(Int128 a); float APS5_VABI __floattisf_nid_postfix(Int128 a); -long double APS5_VABI __floattixf_nid_postfix(Int128 a); +LibcDetail::GuestLongDouble APS5_VABI __floattixf_nid_postfix(Int128 a); double APS5_VABI __floatuntidf_nid_postfix(Uint128 a); float APS5_VABI __floatuntisf_nid_postfix(Uint128 a); -long double APS5_VABI __floatuntixf_nid_postfix(Uint128 a); +LibcDetail::GuestLongDouble APS5_VABI __floatuntixf_nid_postfix(Uint128 a); __complex__ float APS5_VABI __mulsc3_nid_postfix(float a, float b, float c, float d); __complex__ double APS5_VABI __muldc3_nid_postfix(double a, double b, double c, double d); +#ifndef ANYPS5_GUEST_LIFTED __complex__ long double APS5_VABI __mulxc3_nid_postfix(long double a, long double b, long double c, long double d); +#endif __complex__ float APS5_VABI __divsc3_nid_postfix(float a, float b, float c, float d); __complex__ double APS5_VABI __divdc3_nid_postfix(double a, double b, double c, double d); +#ifndef ANYPS5_GUEST_LIFTED __complex__ long double APS5_VABI __divxc3_nid_postfix(long double a, long double b, long double c, long double d); -long double APS5_VABI __powixf2_nid_postfix(long double base, int exponent); +#endif +LibcDetail::GuestLongDouble APS5_VABI __powixf2_nid_postfix(LibcDetail::GuestLongDouble base, int exponent); int APS5_VABI fegetround_nid_postfix(); int APS5_VABI fesetround_nid_postfix(int rounding); int APS5_VABI fetestexcept_nid_postfix(int exceptions); @@ -665,7 +670,7 @@ double APS5_VABI exp2_nid_postfix(double x); double APS5_VABI ldexp_nid_postfix(double x, int exponent); double APS5_VABI scalbn_nid_postfix(double x, int exponent); float APS5_VABI scalbnf_nid_postfix(float x, int exponent); -long double APS5_VABI scalbnl_nid_postfix(long double x, int exponent); +LibcDetail::GuestLongDouble APS5_VABI scalbnl_nid_postfix(LibcDetail::GuestLongDouble x, int exponent); float APS5_VABI significandf_nid_postfix(float x); double APS5_VABI significand_nid_postfix(double x); double APS5_VABI frexp_nid_postfix(double x, int* exponent); @@ -674,13 +679,13 @@ std::int64_t APS5_VABI lround_nid_postfix(double x); std::int64_t APS5_VABI lroundf_nid_postfix(float x); std::int64_t APS5_VABI llround_nid_postfix(double x); std::int64_t APS5_VABI llroundf_nid_postfix(float x); -std::int64_t APS5_VABI llroundl_nid_postfix(long double x); +std::int64_t APS5_VABI llroundl_nid_postfix(LibcDetail::GuestLongDouble x); int APS5_VABI __isfinitef_nid_postfix(float x); int APS5_VABI __isnormal_nid_postfix(double x); int APS5_VABI __isnormalf_nid_postfix(float x); int APS5_VABI __isinff_nid_postfix(float x); float APS5_VABI nearbyintf_nid_postfix(float x); -long double APS5_VABI nearbyintl_nid_postfix(long double x); +LibcDetail::GuestLongDouble APS5_VABI nearbyintl_nid_postfix(LibcDetail::GuestLongDouble x); double APS5_VABI nearbyint_nid_postfix(double x); double APS5_VABI cbrt_nid_postfix(double x); double APS5_VABI asin_nid_postfix(double x); @@ -689,7 +694,7 @@ double APS5_VABI sqrt_nid_postfix(double x); float APS5_VABI sqrtf_nid_postfix(float x); double APS5_VABI fabs_nid_postfix(double x); float APS5_VABI fabsf_nid_postfix(float x); -long double APS5_VABI fabsl_nid_postfix(long double x); +LibcDetail::GuestLongDouble APS5_VABI fabsl_nid_postfix(LibcDetail::GuestLongDouble x); double APS5_VABI copysign_nid_postfix(double x, double y); float APS5_VABI copysignf_nid_postfix(float x, float y); int APS5_VABI abs_nid_postfix(int x); @@ -703,38 +708,38 @@ double APS5_VABI fma_nid_postfix(double x, double y, double z); float APS5_VABI fmaf_nid_postfix(float x, float y, float z); double APS5_VABI fmin_nid_postfix(double x, double y); float APS5_VABI fminf_nid_postfix(float x, float y); -long double APS5_VABI fminl_nid_postfix(long double x, long double y); +LibcDetail::GuestLongDouble APS5_VABI fminl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); double APS5_VABI fmax_nid_postfix(double x, double y); float APS5_VABI fmaxf_nid_postfix(float x, float y); -long double APS5_VABI fmaxl_nid_postfix(long double x, long double y); +LibcDetail::GuestLongDouble APS5_VABI fmaxl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); double APS5_VABI fdim_nid_postfix(double x, double y); float APS5_VABI fdimf_nid_postfix(float x, float y); -long double APS5_VABI acosl_nid_postfix(long double x); -long double APS5_VABI acoshl_nid_postfix(long double x); -long double APS5_VABI asinhl_nid_postfix(long double x); -long double APS5_VABI atanhl_nid_postfix(long double x); -long double APS5_VABI atan2l_nid_postfix(long double y, long double x); -long double APS5_VABI cbrtl_nid_postfix(long double x); -long double APS5_VABI erfl_nid_postfix(long double x); -long double APS5_VABI erfcl_nid_postfix(long double x); -long double APS5_VABI expm1l_nid_postfix(long double x); -long double APS5_VABI log1pl_nid_postfix(long double x); -long double APS5_VABI log2l_nid_postfix(long double x); -long double APS5_VABI lgammal_nid_postfix(long double x); -long double APS5_VABI tgammal_nid_postfix(long double x); +LibcDetail::GuestLongDouble APS5_VABI acosl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI acoshl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI asinhl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI atanhl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI atan2l_nid_postfix(LibcDetail::GuestLongDouble y, LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI cbrtl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI erfl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI erfcl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI expm1l_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI log1pl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI log2l_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI lgammal_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI tgammal_nid_postfix(LibcDetail::GuestLongDouble x); float APS5_VABI tgammaf_nid_postfix(float x); -long double APS5_VABI hypotl_nid_postfix(long double x, long double y); +LibcDetail::GuestLongDouble APS5_VABI hypotl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); float APS5_VABI hypot3f_nid_postfix(float x, float y, float z); double APS5_VABI hypot3_nid_postfix(double x, double y, double z); -long double APS5_VABI hypot3l_nid_postfix(long double x, long double y, long double z); +LibcDetail::GuestLongDouble APS5_VABI hypot3l_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y, LibcDetail::GuestLongDouble z); float APS5_VABI j0f_nid_postfix(float x); float APS5_VABI j1f_nid_postfix(float x); float APS5_VABI jnf_nid_postfix(int order, float x); float APS5_VABI y0f_nid_postfix(float x); float APS5_VABI y1f_nid_postfix(float x); float APS5_VABI ynf_nid_postfix(int order, float x); -long double APS5_VABI asinl_nid_postfix(long double x); -long double APS5_VABI frexpl_nid_postfix(long double x, int* exponent); +LibcDetail::GuestLongDouble APS5_VABI asinl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI frexpl_nid_postfix(LibcDetail::GuestLongDouble x, int* exponent); double APS5_VABI exp_nid_postfix(double x); double APS5_VABI atan_nid_postfix(double x); double APS5_VABI tan_nid_postfix(double x); @@ -801,22 +806,22 @@ double APS5_VABI modf_nid_postfix(double x, double* integral); float APS5_VABI modff_nid_postfix(float x, float* integral); double APS5_VABI tanh_nid_postfix(double x); float APS5_VABI tanhf_nid_postfix(float x); -long double APS5_VABI atanl_nid_postfix(long double x); -long double APS5_VABI sinl_nid_postfix(long double x); -long double APS5_VABI cosl_nid_postfix(long double x); -long double APS5_VABI tanl_nid_postfix(long double x); -long double APS5_VABI sinhl_nid_postfix(long double x); -long double APS5_VABI coshl_nid_postfix(long double x); -long double APS5_VABI tanhl_nid_postfix(long double x); -long double APS5_VABI expl_nid_postfix(long double x); -long double APS5_VABI exp2l_nid_postfix(long double x); -long double APS5_VABI logl_nid_postfix(long double x); -long double APS5_VABI log10l_nid_postfix(long double x); -long double APS5_VABI powl_nid_postfix(long double base, long double exp); -long double APS5_VABI ldexpl_nid_postfix(long double x, int exp); -long double APS5_VABI truncl_nid_postfix(long double x); -long double APS5_VABI ceill_nid_postfix(long double x); -long double APS5_VABI floorl_nid_postfix(long double x); +LibcDetail::GuestLongDouble APS5_VABI atanl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI sinl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI cosl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI tanl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI sinhl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI coshl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI tanhl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI expl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI exp2l_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI logl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI log10l_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI powl_nid_postfix(LibcDetail::GuestLongDouble base, LibcDetail::GuestLongDouble exp); +LibcDetail::GuestLongDouble APS5_VABI ldexpl_nid_postfix(LibcDetail::GuestLongDouble x, int exp); +LibcDetail::GuestLongDouble APS5_VABI truncl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI ceill_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI floorl_nid_postfix(LibcDetail::GuestLongDouble x); double APS5_VABI logb_nid_postfix(double x); int APS5_VABI ilogb_nid_postfix(double x); int APS5_VABI ilogbf_nid_postfix(float x); @@ -835,36 +840,36 @@ int APS5_VABI isnan_nid_postfix(double x); int APS5_VABI isinf_nid_postfix(double x); int APS5_VABI __fpclassifyf_nid_postfix(float x); int APS5_VABI __signbitf_nid_postfix(float x); -int APS5_VABI __signbitl_nid_postfix(long double x); -int APS5_VABI __isfinitel_nid_postfix(long double x); +int APS5_VABI __signbitl_nid_postfix(LibcDetail::GuestLongDouble x); +int APS5_VABI __isfinitel_nid_postfix(LibcDetail::GuestLongDouble x); int APS5_VABI rand_nid_postfix(); int APS5_VABI rand_r_nid_postfix(unsigned int* state); void APS5_VABI srand_nid_postfix(unsigned int seed); int APS5_VABI __fpclassifyd_nid_postfix(double value); double APS5_VABI _Sinh_nid_postfix(double x, double y); double APS5_VABI _Cosh_nid_postfix(double x, double y); -long double APS5_VABI _LCosh_nid_postfix(long double x, long double y); -long double APS5_VABI _LSinh_nid_postfix(long double x, long double y); +LibcDetail::GuestLongDouble APS5_VABI _LCosh_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); +LibcDetail::GuestLongDouble APS5_VABI _LSinh_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); int APS5_VABI _Fltrounds_nid_postfix(void); int APS5_VABI fegetenv_nid_postfix(GuestFloatingPointEnvironment* environment); int APS5_VABI fesetenv_nid_postfix(const GuestFloatingPointEnvironment* environment); -long double APS5_VABI copysignl_nid_postfix(long double x, long double y); -long double APS5_VABI fdiml_nid_postfix(long double x, long double y); -long double APS5_VABI fmodl_nid_postfix(long double x, long double y); -long double APS5_VABI remainderl_nid_postfix(long double x, long double y); -long double APS5_VABI remquol_nid_postfix(long double x, long double y, int* quotient); -long double APS5_VABI logbl_nid_postfix(long double x); -int APS5_VABI ilogbl_nid_postfix(long double x); -long double APS5_VABI rintl_nid_postfix(long double x); -long double APS5_VABI roundl_nid_postfix(long double x); -std::int64_t APS5_VABI lroundl_nid_postfix(long double x); -std::int64_t APS5_VABI lrintl_nid_postfix(long double x); -long long APS5_VABI llrintl_nid_postfix(long double x); -long double APS5_VABI sqrtl_nid_postfix(long double x); -long double APS5_VABI modfl_nid_postfix(long double x, long double* integral); -long double APS5_VABI nanl_nid_postfix(const char*); -long double APS5_VABI scalblnl_nid_postfix(long double x, std::int64_t exponent); -long double APS5_VABI fmal_nid_postfix(long double x, long double y, long double z); +LibcDetail::GuestLongDouble APS5_VABI copysignl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); +LibcDetail::GuestLongDouble APS5_VABI fdiml_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); +LibcDetail::GuestLongDouble APS5_VABI fmodl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); +LibcDetail::GuestLongDouble APS5_VABI remainderl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y); +LibcDetail::GuestLongDouble APS5_VABI remquol_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y, int* quotient); +LibcDetail::GuestLongDouble APS5_VABI logbl_nid_postfix(LibcDetail::GuestLongDouble x); +int APS5_VABI ilogbl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI rintl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI roundl_nid_postfix(LibcDetail::GuestLongDouble x); +std::int64_t APS5_VABI lroundl_nid_postfix(LibcDetail::GuestLongDouble x); +std::int64_t APS5_VABI lrintl_nid_postfix(LibcDetail::GuestLongDouble x); +long long APS5_VABI llrintl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI sqrtl_nid_postfix(LibcDetail::GuestLongDouble x); +LibcDetail::GuestLongDouble APS5_VABI modfl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble* integral); +LibcDetail::GuestLongDouble APS5_VABI nanl_nid_postfix(const char*); +LibcDetail::GuestLongDouble APS5_VABI scalblnl_nid_postfix(LibcDetail::GuestLongDouble x, std::int64_t exponent); +LibcDetail::GuestLongDouble APS5_VABI fmal_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y, LibcDetail::GuestLongDouble z); double APS5_VABI rint_nid_postfix(double x); float APS5_VABI rintf_nid_postfix(float x); std::int64_t APS5_VABI lrint_nid_postfix(double x); @@ -879,20 +884,20 @@ double APS5_VABI scalb_nid_postfix(double x, double exponent); float APS5_VABI scalbf_nid_postfix(float x, float exponent); int APS5_VABI finite_nid_postfix(double x); int APS5_VABI finitef_nid_postfix(float x); -int APS5_VABI __fpclassifyl_nid_postfix(long double x); -int APS5_VABI __isinfl_nid_postfix(long double x); -int APS5_VABI __isnanl_nid_postfix(long double x); -int APS5_VABI __isnormall_nid_postfix(long double x); -short APS5_VABI _LDtest_nid_postfix(const long double* value); +int APS5_VABI __fpclassifyl_nid_postfix(LibcDetail::GuestLongDouble x); +int APS5_VABI __isinfl_nid_postfix(LibcDetail::GuestLongDouble x); +int APS5_VABI __isnanl_nid_postfix(LibcDetail::GuestLongDouble x); +int APS5_VABI __isnormall_nid_postfix(LibcDetail::GuestLongDouble x); +short APS5_VABI _LDtest_nid_postfix(const LibcDetail::GuestLongDouble* value); short APS5_VABI _Dint_nid_postfix(double*, short); short APS5_VABI _FDint_nid_postfix(float*, short); -short APS5_VABI _LDint_nid_postfix(long double*, short); +short APS5_VABI _LDint_nid_postfix(LibcDetail::GuestLongDouble*, short); short APS5_VABI _Dscale_nid_postfix(double*, std::int64_t); short APS5_VABI _FDscale_nid_postfix(float*, std::int64_t); -short APS5_VABI _LDscale_nid_postfix(long double*, std::int64_t); +short APS5_VABI _LDscale_nid_postfix(LibcDetail::GuestLongDouble*, std::int64_t); short APS5_VABI _Dunscale_nid_postfix(short*, double*); short APS5_VABI _FDunscale_nid_postfix(short*, float*); -short APS5_VABI _LDunscale_nid_postfix(short*, long double*); +short APS5_VABI _LDunscale_nid_postfix(short*, LibcDetail::GuestLongDouble*); short APS5_VABI _ZNSt8__sce_v27_DscaleEPdl_nid_postfix(double* value, std::int64_t exponent); short APS5_VABI _ZNSt8__sce_v28_FDscaleEPfl_nid_postfix(float* value, std::int64_t exponent); short APS5_VABI _ZNSt8__sce_v28_LDscaleEPel_nid_postfix(double* value, std::int64_t exponent); @@ -912,14 +917,14 @@ short APS5_VABI _ZNSt8__sce_v26_DnormEPNS_5_DvalE_nid_postfix(std::uint16_t* wor short APS5_VABI _ZNSt8__sce_v27_FDnormEPNS_5_FvalE_nid_postfix(std::uint16_t* words); int APS5_VABI _Fpcomp_nid_postfix(double, double); int APS5_VABI _FFpcomp_nid_postfix(float, float); -int APS5_VABI _LFpcomp_nid_postfix(long double, long double); +int APS5_VABI _LFpcomp_nid_postfix(LibcDetail::GuestLongDouble, LibcDetail::GuestLongDouble); double APS5_VABI _Poly_nid_postfix(double, const double*, int); float APS5_VABI _FPoly_nid_postfix(float, const float*, int); -long double APS5_VABI _LPoly_nid_postfix(long double, const long double*, int); +LibcDetail::GuestLongDouble APS5_VABI _LPoly_nid_postfix(LibcDetail::GuestLongDouble, const LibcDetail::GuestLongDouble*, int); int APS5_VABI _Dclass_nid_postfix(double x); int APS5_VABI _FDclass_nid_postfix(float x); -int APS5_VABI _LDclass_nid_postfix(long double x); -short APS5_VABI _LDsign_nid_postfix(long double x); +int APS5_VABI _LDclass_nid_postfix(LibcDetail::GuestLongDouble x); +short APS5_VABI _LDsign_nid_postfix(LibcDetail::GuestLongDouble x); void* APS5_VABI sceLibcMspaceCreate_nid_postfix(const char* name, void* base, std::size_t size, unsigned flags); int APS5_VABI sceLibcMspaceDestroy_nid_postfix(void* handle); void* APS5_VABI sceLibcMspaceMalloc_nid_postfix(void* handle, std::size_t size); @@ -1163,7 +1168,7 @@ unsigned long long APS5_VABI strtouq_nid_postfix(const char* str, char** endptr, double APS5_VABI strtod_nid_postfix(const char* str, char** endptr); double APS5_VABI atof_nid_postfix(const char* str); float APS5_VABI strtof_nid_postfix(const char* str, char** endptr); -long double APS5_VABI strtold_nid_postfix(const char* str, char** endptr); +LibcDetail::GuestLongDouble APS5_VABI strtold_nid_postfix(const char* str, char** endptr); int APS5_VABI atoi_nid_postfix(const char* str); std::int64_t APS5_VABI atol_nid_postfix(const char* str); long long APS5_VABI atoll_nid_postfix(const char* str); @@ -1206,7 +1211,7 @@ char16_t* APS5_VABI wcstok_nid_postfix(char16_t* text, const char16_t* delimiter char16_t* APS5_VABI wmemset_nid_postfix(char16_t* s, char16_t c, size_t n); double APS5_VABI wcstod_nid_postfix(const char16_t* str, char16_t** endptr); float APS5_VABI wcstof_nid_postfix(const char16_t* str, char16_t** endptr); -long double APS5_VABI wcstold_nid_postfix(const char16_t* str, char16_t** endptr); +LibcDetail::GuestLongDouble APS5_VABI wcstold_nid_postfix(const char16_t* str, char16_t** endptr); unsigned long long APS5_VABI wcstoul_nid_postfix(const char16_t* str, char16_t** endptr, int base); unsigned long long APS5_VABI wcstoull_nid_postfix(const char16_t* str, char16_t** endptr, int base); long long APS5_VABI wcstoll_nid_postfix(const char16_t* str, char16_t** endptr, int base); @@ -1677,14 +1682,14 @@ void APS5_VABI srandomdev_nid_postfix(); double APS5_VABI _Stodx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error); float APS5_VABI _Stofx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error); -long double APS5_VABI _Stoldx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error); +LibcDetail::GuestLongDouble APS5_VABI _Stoldx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error); unsigned long long APS5_VABI _WStoull_nid_postfix(const char16_t* str, char16_t** endptr, int base); long long APS5_VABI _WStoll_nid_postfix(const char16_t* str, char16_t** endptr, int base); double APS5_VABI _WStod_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten); float APS5_VABI _WStof_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten); -long double APS5_VABI _WStold_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten); +LibcDetail::GuestLongDouble APS5_VABI _WStold_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten); float APS5_VABI _Stof_nid_postfix(const char* text, char** end, std::int64_t pten); -long double APS5_VABI _Stold_nid_postfix(const char* text, char** end, std::int64_t pten); +LibcDetail::GuestLongDouble APS5_VABI _Stold_nid_postfix(const char* text, char** end, std::int64_t pten); long long APS5_VABI _Stollx_nid_postfix(const char* text, char** end, int base, int* error); unsigned long long APS5_VABI _Stoullx_nid_postfix(const char* text, char** end, int base, int* error); std::uint64_t APS5_VABI _Stoulx_nid_postfix(const char* text, char** end, int base, int* error); diff --git a/core/libs/prx/libc/include/GuestLongDouble.hpp b/core/libs/prx/libc/include/GuestLongDouble.hpp index f6c312b3b4b1..9f6d0bb11e1d 100644 --- a/core/libs/prx/libc/include/GuestLongDouble.hpp +++ b/core/libs/prx/libc/include/GuestLongDouble.hpp @@ -1,26 +1,440 @@ #ifndef CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLONGDOUBLE_HPP #define CORE_LIBS_PRX_LIBC_INCLUDE_GUESTLONGDOUBLE_HPP +#include #include +#include #include #include #include #include +#include #include #include #include #include +#include -#if defined(ANYPS5_GUEST_LIFTED) && LDBL_MANT_DIG != 64 -extern "C" void GuestCpuStoreExtended_nid_no_patch(const long double* host, void* guest); +#ifdef ANYPS5_GUEST_LIFTED +#include "prx/libc/include/GuestCpu.hpp" + +extern "C" { +void GuestCpuExtendedAdd_nid_no_patch(const void* left, const void* right, void* result); +void GuestCpuExtendedSubtract_nid_no_patch(const void* left, const void* right, void* result); +void GuestCpuExtendedMultiply_nid_no_patch(const void* left, const void* right, void* result); +void GuestCpuExtendedDivide_nid_no_patch(const void* left, const void* right, void* result); +void GuestCpuExtendedSquareRoot_nid_no_patch(const void* value, void* result); +void GuestCpuExtendedRoundToIntegral_nid_no_patch(const void* value, void* result); +double GuestCpuExtendedToDouble_nid_no_patch(const void* value); +float GuestCpuExtendedToFloat_nid_no_patch(const void* value); +std::int64_t GuestCpuExtendedToInt64_nid_no_patch(const void* value); +#if LDBL_MANT_DIG == 113 +void GuestCpuStoreExtended_nid_no_patch(const long double* host, void* guest); +#endif +} #endif namespace LibcDetail { inline constexpr std::size_t GuestLongDoubleBytes = 16; +enum class IntegralRounding { TowardZero, Downward, Upward, HalfAwayFromZero }; + +#ifdef ANYPS5_GUEST_LIFTED + +class alignas(16) GuestLongDouble { +public: + constexpr GuestLongDouble() = default; + constexpr GuestLongDouble(int value) : GuestLongDouble(static_cast(value)) {} + constexpr GuestLongDouble(long value) : GuestLongDouble(static_cast(value)) {} + constexpr GuestLongDouble(long long value) + : GuestLongDouble(FromInteger(value < 0, value < 0 ? 0ull - static_cast(value) : static_cast(value))) {} + constexpr GuestLongDouble(unsigned value) : GuestLongDouble(FromInteger(false, value)) {} + constexpr GuestLongDouble(unsigned long value) : GuestLongDouble(FromInteger(false, value)) {} + constexpr GuestLongDouble(unsigned long long value) : GuestLongDouble(FromInteger(false, value)) {} + constexpr GuestLongDouble(float value) : GuestLongDouble(static_cast(value)) {} + constexpr GuestLongDouble(double value) : GuestLongDouble(FromDouble(std::bit_cast(value))) {} + + static constexpr GuestLongDouble FromBits(const std::uint16_t signExponent, const std::uint64_t significand) { + GuestLongDouble value; + value._signExponent = signExponent; + value._significand = significand; + return value; + } + + constexpr std::uint16_t SignExponent() const { return _signExponent; } + constexpr std::uint64_t Significand() const { return _significand; } + constexpr int Exponent() const { return _signExponent & 0x7fff; } + constexpr bool Negative() const { return (_signExponent & 0x8000) != 0; } + constexpr bool Nan() const { return Exponent() == 0x7fff && (_significand << 1) != 0; } + constexpr bool Infinite() const { return Exponent() == 0x7fff && (_significand << 1) == 0; } + constexpr bool Zero() const { return _significand == 0 && Exponent() != 0x7fff; } + + explicit operator double() const { return GuestCpuExtendedToDouble_nid_no_patch(this); } + explicit operator float() const { return GuestCpuExtendedToFloat_nid_no_patch(this); } + + template + requires std::is_integral_v + explicit operator TInteger() const { + if constexpr (std::is_unsigned_v && sizeof(TInteger) == sizeof(std::uint64_t)) { + constexpr auto Half = FromBits(0x403e, 0x8000000000000000ull); + if (!(*this < Half)) { + const auto shifted = *this - Half; + return static_cast(GuestCpuExtendedToInt64_nid_no_patch(&shifted)) + (TInteger {1} << 63); + } + } + return static_cast(GuestCpuExtendedToInt64_nid_no_patch(this)); + } + + friend GuestLongDouble operator+(const GuestLongDouble& left, const GuestLongDouble& right) { + GuestLongDouble result; + GuestCpuExtendedAdd_nid_no_patch(&left, &right, &result); + return result; + } + + friend GuestLongDouble operator-(const GuestLongDouble& left, const GuestLongDouble& right) { + GuestLongDouble result; + GuestCpuExtendedSubtract_nid_no_patch(&left, &right, &result); + return result; + } + + friend GuestLongDouble operator*(const GuestLongDouble& left, const GuestLongDouble& right) { + GuestLongDouble result; + GuestCpuExtendedMultiply_nid_no_patch(&left, &right, &result); + return result; + } + + friend GuestLongDouble operator/(const GuestLongDouble& left, const GuestLongDouble& right) { + GuestLongDouble result; + GuestCpuExtendedDivide_nid_no_patch(&left, &right, &result); + return result; + } + + friend constexpr GuestLongDouble operator-(const GuestLongDouble& value) { + return FromBits(static_cast(value._signExponent ^ 0x8000), value._significand); + } + + GuestLongDouble& operator+=(const GuestLongDouble& other) { return *this = *this + other; } + GuestLongDouble& operator-=(const GuestLongDouble& other) { return *this = *this - other; } + GuestLongDouble& operator*=(const GuestLongDouble& other) { return *this = *this * other; } + GuestLongDouble& operator/=(const GuestLongDouble& other) { return *this = *this / other; } + + friend constexpr std::partial_ordering operator<=>(const GuestLongDouble& left, const GuestLongDouble& right) { + if (left.Nan() || right.Nan()) return std::partial_ordering::unordered; + const auto first = left.Ordered(); + const auto second = right.Ordered(); + return first < second ? std::partial_ordering::less : first > second ? std::partial_ordering::greater : std::partial_ordering::equivalent; + } + + friend constexpr bool operator==(const GuestLongDouble& left, const GuestLongDouble& right) { return (left <=> right) == 0; } + +private: + std::uint64_t _significand = 0; + std::uint16_t _signExponent = 0; + std::uint16_t _padding[3] = {}; + + static constexpr GuestLongDouble FromInteger(const bool negative, const unsigned long long magnitude) { + if (magnitude == 0) return {}; + const int shift = std::countl_zero(magnitude); + return FromBits(static_cast((negative ? 0x8000 : 0) | (16383 + 63 - shift)), magnitude << shift); + } + + static constexpr GuestLongDouble FromDouble(const std::uint64_t bits) { + const auto sign = static_cast((bits >> 48) & 0x8000); + const auto exponent = static_cast((bits >> 52) & 0x7ff); + const auto fraction = bits & 0xfffffffffffffull; + if (exponent == 0x7ff) return FromBits(static_cast(sign | 0x7fff), fraction == 0 ? 0x8000000000000000ull : 0xc000000000000000ull | (fraction << 11)); + if (exponent == 0) { + if (fraction == 0) return FromBits(sign, 0); + const int shift = std::countl_zero(fraction); + return FromBits(static_cast(sign | (15372 - shift)), fraction << shift); + } + return FromBits(static_cast(sign | (exponent - 1023 + 16383)), 0x8000000000000000ull | (fraction << 11)); + } + + constexpr __int128 Ordered() const { + if (_significand == 0) return 0; + const int exponent = Exponent() == 0 && (_significand >> 63) != 0 ? 1 : Exponent(); + const auto magnitude = static_cast<__int128>(static_cast(exponent) << 64 | _significand); + return Negative() ? -magnitude : magnitude; + } +}; + +static_assert(sizeof(GuestLongDouble) == GuestLongDoubleBytes && std::is_trivially_copyable_v); + +} + +template<> +struct std::numeric_limits { + using Value = LibcDetail::GuestLongDouble; + static constexpr bool is_specialized = true; + static constexpr bool is_signed = true; + static constexpr bool is_integer = false; + static constexpr bool is_exact = false; + static constexpr bool has_infinity = true; + static constexpr bool has_quiet_NaN = true; + static constexpr bool has_signaling_NaN = true; + static constexpr bool is_iec559 = true; + static constexpr bool is_bounded = true; + static constexpr bool is_modulo = false; + static constexpr bool traps = false; + static constexpr bool tinyness_before = false; + static constexpr int digits = 64; + static constexpr int digits10 = 18; + static constexpr int max_digits10 = 21; + static constexpr int radix = 2; + static constexpr int min_exponent = -16381; + static constexpr int min_exponent10 = -4931; + static constexpr int max_exponent = 16384; + static constexpr int max_exponent10 = 4932; + static constexpr std::float_round_style round_style = std::round_to_nearest; + static constexpr Value min() noexcept { return Value::FromBits(0x0001, 0x8000000000000000ull); } + static constexpr Value lowest() noexcept { return Value::FromBits(0xfffe, 0xffffffffffffffffull); } + static constexpr Value max() noexcept { return Value::FromBits(0x7ffe, 0xffffffffffffffffull); } + static constexpr Value epsilon() noexcept { return Value::FromBits(0x3fc0, 0x8000000000000000ull); } + static constexpr Value round_error() noexcept { return Value::FromBits(0x3ffe, 0x8000000000000000ull); } + static constexpr Value infinity() noexcept { return Value::FromBits(0x7fff, 0x8000000000000000ull); } + static constexpr Value quiet_NaN() noexcept { return Value::FromBits(0x7fff, 0xc000000000000000ull); } + static constexpr Value signaling_NaN() noexcept { return Value::FromBits(0x7fff, 0xa000000000000000ull); } + static constexpr Value denorm_min() noexcept { return Value::FromBits(0x0000, 0x0000000000000001ull); } +}; + +namespace LibcDetail { + +#if LDBL_MANT_DIG >= 64 +using HostWide = long double; +#else +using HostWide = double; +#endif + +#if LDBL_MANT_DIG == 64 +inline HostWide ToWide(const GuestLongDouble value) { + HostWide wide; + std::memcpy(&wide, &value, sizeof(wide)); + return wide; +} + +inline GuestLongDouble FromWide(const HostWide wide) { + GuestLongDouble value; + std::memcpy(&value, &wide, 10); + return value; +} +#elif LDBL_MANT_DIG == 113 +inline HostWide ToWide(const GuestLongDouble value) { + if (value.Nan() || value.Infinite()) { + const std::uint64_t fraction = value.Significand() & 0x7FFF'FFFF'FFFF'FFFFull; + const std::uint64_t low = fraction << 49; + const std::uint64_t high = (fraction >> 15) | (static_cast(value.SignExponent()) << 48); + HostWide wide; + std::memcpy(&wide, &low, sizeof(low)); + std::memcpy(reinterpret_cast(&wide) + 8, &high, sizeof(high)); + return wide; + } + const int exponent = value.Exponent() == 0 ? 1 : value.Exponent(); + const HostWide magnitude = std::ldexp(static_cast(value.Significand()), exponent - 16383 - 63); + return value.Negative() ? -magnitude : magnitude; +} + +inline GuestLongDouble FromWide(const HostWide wide) { + unsigned char bytes[GuestLongDoubleBytes] = {}; + GuestCpuStoreExtended_nid_no_patch(&wide, bytes); + GuestLongDouble value; + std::memcpy(&value, bytes, 10); + return value; +} +#else +inline HostWide ToWide(const GuestLongDouble value) { + return static_cast(value); +} + +inline GuestLongDouble FromWide(const HostWide wide) { + return GuestLongDouble(wide); +} +#endif + +inline GuestLongDouble GuestLongDoubleLoad(const void* source) { + GuestLongDouble value; + std::memcpy(&value, source, 10); + return value; +} + +inline void GuestLongDoubleStore(void* target, const GuestLongDouble value) { + std::memcpy(target, &value, 10); +} + +inline std::array GuestLongDoubleImage(const GuestLongDouble value) { + return std::bit_cast>(value); +} + +GuestLongDouble GuestLongDoubleParse(const char* text, char** end); + +constexpr bool IsNan(const GuestLongDouble value) { return value.Nan(); } +constexpr bool IsInf(const GuestLongDouble value) { return value.Infinite(); } +constexpr bool IsFinite(const GuestLongDouble value) { return value.Exponent() != 0x7fff; } +constexpr bool SignBit(const GuestLongDouble value) { return value.Negative(); } +constexpr GuestLongDouble Fabs(const GuestLongDouble value) { return GuestLongDouble::FromBits(static_cast(value.SignExponent() & 0x7fff), value.Significand()); } + +constexpr GuestLongDouble CopySign(const GuestLongDouble magnitude, const GuestLongDouble sign) { + return GuestLongDouble::FromBits(static_cast((magnitude.SignExponent() & 0x7fff) | (sign.SignExponent() & 0x8000)), magnitude.Significand()); +} + +constexpr GuestLongDouble Frexp(const GuestLongDouble value, int* exponent) { + if (value.Zero() || !IsFinite(value)) { + *exponent = 0; + return value; + } + int biased = value.Exponent(); + auto significand = value.Significand(); + if (biased == 0) { + const int shift = std::countl_zero(significand); + significand <<= shift; + biased = 1 - shift; + } + *exponent = biased - 16382; + return GuestLongDouble::FromBits(static_cast((value.SignExponent() & 0x8000) | 16382), significand); +} + +inline GuestLongDouble Scalbn(const GuestLongDouble value, const long exponent) { + if (!IsFinite(value) || value.Zero() || exponent == 0) return value; + int current = 0; + const auto fraction = Frexp(value, ¤t); + const long target = current + exponent; + const auto sign = static_cast(value.SignExponent() & 0x8000); + if (target + 16382 >= 1 && target + 16382 <= 0x7ffe) return GuestLongDouble::FromBits(static_cast(sign | (target + 16382)), fraction.Significand()); + const long start = target > 0 ? 16383 : -16381; + const long rest = std::clamp(target - start, -16382l, 16383l); + return GuestLongDouble::FromBits(static_cast(sign | (start + 16382)), fraction.Significand()) * GuestLongDouble::FromBits(static_cast(16383 + rest), 0x8000000000000000ull); +} + +constexpr int Ilogb(const GuestLongDouble value) { + if (value.Nan()) return FP_ILOGBNAN; + if (value.Infinite()) return std::numeric_limits::max(); + if (value.Zero()) return FP_ILOGB0; + int exponent = 0; + Frexp(value, &exponent); + return exponent - 1; +} + +inline GuestLongDouble RoundIntegral(const GuestLongDouble value, const IntegralRounding rounding) { + if (!IsFinite(value) || value.Zero()) return value; + const int unbiased = value.Exponent() - 16383; + if (unbiased >= 63) return value; + const bool negative = value.Negative(); + const auto magnitude = value.Significand(); + const int fractionBits = unbiased < 0 ? 64 : 63 - unbiased; + const auto whole = fractionBits >= 64 ? 0 : magnitude >> fractionBits; + const auto fraction = fractionBits >= 64 ? magnitude : magnitude & ((1ull << fractionBits) - 1); + const bool half = fractionBits <= 64 && fractionBits > 0 && (fractionBits == 64 ? unbiased == -1 : (fraction >> (fractionBits - 1)) != 0); + bool away = false; + switch (rounding) { + case IntegralRounding::TowardZero: away = false; break; + case IntegralRounding::Downward: away = negative && fraction != 0; break; + case IntegralRounding::Upward: away = !negative && fraction != 0; break; + case IntegralRounding::HalfAwayFromZero: away = half; break; + } + const GuestLongDouble integral(whole + (away ? 1ull : 0ull)); + return CopySign(integral, value); +} + +inline GuestLongDouble Remainder(const GuestLongDouble dividend, const GuestLongDouble divisor, const bool nearest, long long* quotient) { + *quotient = 0; + if (dividend.Nan() || divisor.Nan() || dividend.Infinite() || divisor.Zero()) return (dividend * divisor) / (dividend * divisor); + if (divisor.Infinite() || dividend.Zero()) return dividend; + int dividendExponent = 0; + int divisorExponent = 0; + const auto dividendSignificand = Frexp(Fabs(dividend), ÷ndExponent).Significand(); + const auto divisorSignificand = Frexp(Fabs(divisor), &divisorExponent).Significand(); + if (dividendExponent < divisorExponent) { + if (!nearest || dividendExponent < divisorExponent - 1 || dividendSignificand <= divisorSignificand) return dividend; + *quotient = SignBit(dividend) != SignBit(divisor) ? -1 : 1; + return dividend - CopySign(Fabs(divisor), dividend); + } + unsigned __int128 remainder = dividendSignificand; + unsigned long long bits = 0; + for (int exponent = dividendExponent; exponent >= divisorExponent; --exponent) { + bits <<= 1; + if (remainder >= divisorSignificand) { + remainder -= divisorSignificand; + bits |= 1; + } + if (exponent != divisorExponent) remainder <<= 1; + } + auto magnitude = static_cast(remainder); + auto sign = dividend; + if (nearest && (remainder << 1 > divisorSignificand || (remainder << 1 == divisorSignificand && (bits & 1) != 0))) { + magnitude = divisorSignificand - magnitude; + ++bits; + sign = -dividend; + } + *quotient = SignBit(dividend) != SignBit(divisor) ? -static_cast(bits & 0x7fffffffffffffffull) : static_cast(bits & 0x7fffffffffffffffull); + return CopySign(Scalbn(GuestLongDouble(magnitude), divisorExponent - 64), sign); +} + +inline GuestLongDouble Sqrt(const GuestLongDouble value) { + GuestLongDouble result; + GuestCpuExtendedSquareRoot_nid_no_patch(&value, &result); + return result; +} + +inline GuestLongDouble Rint(const GuestLongDouble value) { + GuestLongDouble result; + GuestCpuExtendedRoundToIntegral_nid_no_patch(&value, &result); + return result; +} + +inline GuestLongDouble NearbyInt(const GuestLongDouble value) { + const auto status = GuestCpu::X87Status(); + const auto result = Rint(value); + GuestCpu::SetX87Status(status); + return result; +} + +inline GuestLongDouble Fma(const GuestLongDouble left, const GuestLongDouble right, const GuestLongDouble addend) { + return FromWide(std::fma(ToWide(left), ToWide(right), ToWide(addend))); +} + +inline GuestLongDouble Logb(const GuestLongDouble value) { + if (value.Nan()) return value; + if (value.Infinite()) return Fabs(value); + if (value.Zero()) return GuestLongDouble(-1) / Fabs(value); + return GuestLongDouble(Ilogb(value)); +} + +inline std::uint64_t NormalizedSignificand(const GuestLongDouble value, int* exponent) { + return Frexp(Fabs(value), exponent).Significand(); +} + +inline long long LlRound(const GuestLongDouble value) { + return static_cast(RoundIntegral(value, IntegralRounding::HalfAwayFromZero)); +} + +inline long long LlRint(const GuestLongDouble value) { + return static_cast(Rint(value)); +} + +inline GuestLongDouble Modf(const GuestLongDouble value, GuestLongDouble* whole) { + if (!IsFinite(value)) { + *whole = value; + return value.Nan() ? value : CopySign(GuestLongDouble {}, value); + } + *whole = RoundIntegral(value, IntegralRounding::TowardZero); + return CopySign(value - *whole, value); +} + +#else + +using GuestLongDouble = long double; +using HostWide = long double; + +inline long double Fma(const long double left, const long double right, const long double addend) { + return std::fma(left, right, addend); +} + static_assert(std::numeric_limits::digits == 64 || std::numeric_limits::digits == 113); +inline HostWide ToWide(const GuestLongDouble value) { return value; } +inline GuestLongDouble FromWide(const HostWide value) { return value; } + inline long double GuestLongDoubleLoad(const void* source) { long double value; static_assert(sizeof(value) == GuestLongDoubleBytes); @@ -44,9 +458,6 @@ inline void GuestLongDoubleStore(void* target, long double value) { if constexpr (std::numeric_limits::digits == 64) { std::memcpy(target, &value, sizeof(value)); } else { -#ifdef ANYPS5_GUEST_LIFTED - GuestCpuStoreExtended_nid_no_patch(&value, target); -#else std::uint64_t low; std::uint64_t high; std::memcpy(&low, &value, sizeof(low)); @@ -59,7 +470,6 @@ inline void GuestLongDoubleStore(void* target, long double value) { std::memcpy(bytes, &mantissa, sizeof(mantissa)); std::memcpy(bytes + 8, &signExponent, sizeof(signExponent)); std::memcpy(target, bytes, sizeof(bytes)); -#endif } } @@ -73,6 +483,108 @@ inline long double GuestLongDoubleParse(const char* text, char** end) { return std::strtold(text, end); } +#endif + +template + requires std::is_floating_point_v +bool IsNan(const T value) { return std::isnan(value); } + +template + requires std::is_floating_point_v +bool IsInf(const T value) { return std::isinf(value); } + +template + requires std::is_floating_point_v +bool IsFinite(const T value) { return std::isfinite(value); } + +template + requires std::is_floating_point_v +bool SignBit(const T value) { return std::signbit(value); } + +template + requires std::is_floating_point_v +T Fabs(const T value) { return std::fabs(value); } + +template + requires std::is_floating_point_v +T CopySign(const T magnitude, const T sign) { return std::copysign(magnitude, sign); } + +template + requires std::is_floating_point_v +T Frexp(const T value, int* exponent) { return std::frexp(value, exponent); } + +template + requires std::is_floating_point_v +T Scalbn(const T value, const long exponent) { return std::scalbln(value, exponent); } + +template + requires std::is_floating_point_v +int Ilogb(const T value) { return std::ilogb(value); } + +template + requires std::is_floating_point_v +T Sqrt(const T value) { return std::sqrt(value); } + +template + requires std::is_floating_point_v +T Rint(const T value) { return std::rint(value); } + +template + requires std::is_floating_point_v +T NearbyInt(const T value) { return std::nearbyint(value); } + +template + requires std::is_floating_point_v +T Logb(const T value) { return std::logb(value); } + +template + requires std::is_floating_point_v +std::uint64_t NormalizedSignificand(const T value, int* exponent) { return static_cast(std::ldexp(std::frexp(std::fabs(value), exponent), 64)); } + +template + requires std::is_floating_point_v +long long LlRound(const T value) { return std::llround(value); } + +template + requires std::is_floating_point_v +long long LlRint(const T value) { return std::llrint(value); } + +template + requires std::is_floating_point_v +T Modf(const T value, T* whole) { return std::modf(value, whole); } + +template + requires std::is_floating_point_v +T RoundIntegral(const T value, const IntegralRounding rounding) { + switch (rounding) { + case IntegralRounding::TowardZero: return std::trunc(value); + case IntegralRounding::Downward: return std::floor(value); + case IntegralRounding::Upward: return std::ceil(value); + case IntegralRounding::HalfAwayFromZero: return std::round(value); + } + return value; +} + +template + requires std::is_floating_point_v +T Remainder(const T dividend, const T divisor, const bool nearest, long long* quotient) { + if (nearest) { + int bits = 0; + const T result = std::remquo(dividend, divisor, &bits); + *quotient = bits; + return result; + } + *quotient = 0; + return std::fmod(dividend, divisor); } +} + +#ifdef ANYPS5_GUEST_LIFTED +#define APS5_GUEST_LONG_DOUBLE(literal, signExponent, significand) ::LibcDetail::GuestLongDouble::FromBits(signExponent, significand) +#else +#define APS5_GUEST_LONG_DOUBLE(literal, signExponent, significand) literal +#endif + + #endif diff --git a/core/libs/prx/libc/include/ScanFloatField.hpp b/core/libs/prx/libc/include/ScanFloatField.hpp new file mode 100644 index 000000000000..5e8b7cb5c934 --- /dev/null +++ b/core/libs/prx/libc/include/ScanFloatField.hpp @@ -0,0 +1,82 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_SCANFLOATFIELD_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_SCANFLOATFIELD_HPP + +#include +#include + +namespace LibcDetail { + +enum class Fit { Invalid, Prefix, Valid }; + +inline int DigitValue(char character) { + if (character >= '0' && character <= '9') return character - '0'; + if (character >= 'a' && character <= 'z') return character - 'a' + 10; + if (character >= 'A' && character <= 'Z') return character - 'A' + 10; + return 36; +} + +inline bool IsDecimal(char character) { + return character >= '0' && character <= '9'; +} + +inline bool IsHex(char character) { + return DigitValue(character) < 16; +} + +inline char Lower(char character) { + return character >= 'A' && character <= 'Z' ? static_cast(character - 'A' + 'a') : character; +} + +inline bool StartsWord(std::string_view text, std::string_view word) { + if (text.size() > word.size()) return false; + for (std::size_t index = 0; index < text.size(); ++index) { + if (Lower(text[index]) != word[index]) return false; + } + return true; +} + +inline std::string_view Unsigned(std::string_view text) { + if (!text.empty() && (text[0] == '+' || text[0] == '-')) text.remove_prefix(1); + return text; +} + +inline Fit SpecialFloatFit(std::string_view text) { + if (StartsWord(text, "infinity")) return text.size() == 3 || text.size() == 8 ? Fit::Valid : Fit::Prefix; + if (StartsWord(text, "nan")) return text.size() == 3 ? Fit::Valid : Fit::Prefix; + if (text.size() < 4 || !StartsWord(text.substr(0, 3), "nan") || text[3] != '(') return Fit::Invalid; + for (std::size_t index = 4; index < text.size(); ++index) { + const char character = text[index]; + if (character == ')') return index + 1 == text.size() ? Fit::Valid : Fit::Invalid; + if (DigitValue(character) == 36 && character != '_') return Fit::Invalid; + } + return Fit::Prefix; +} + +inline Fit FloatFit(std::string_view text) { + text = Unsigned(text); + if (text.empty()) return Fit::Prefix; + if (Lower(text[0]) == 'i' || Lower(text[0]) == 'n') return SpecialFloatFit(text); + const bool hex = text.size() >= 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X'); + const auto digit = [hex](char character) { return hex ? IsHex(character) : IsDecimal(character); }; + std::size_t index = hex ? 2 : 0; + std::size_t digits = 0; + for (; index < text.size() && digit(text[index]); ++index) ++digits; + if (index < text.size() && text[index] == '.') { + for (++index; index < text.size() && digit(text[index]); ++index) ++digits; + } + if (index == text.size()) return digits != 0 ? Fit::Valid : Fit::Prefix; + if (digits == 0) return Fit::Invalid; + if (Lower(text[index]) != (hex ? 'p' : 'e')) return Fit::Invalid; + ++index; + if (index == text.size()) return Fit::Valid; + if (text[index] == '+' || text[index] == '-') ++index; + if (index == text.size()) return Fit::Valid; + for (; index < text.size(); ++index) { + if (!IsDecimal(text[index])) return Fit::Invalid; + } + return Fit::Valid; +} + +} + +#endif diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index b7232d2f81c1..77ecb9a87596 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -63,7 +63,7 @@ class FormatArguments { numbered.resize(classes.size()); for (size_t index = 0; index < classes.size(); ++index) { const auto store = [&](auto value) { std::memcpy(numbered[index].data(), &value, sizeof(value)); }; - if (classes[index] == ArgumentClass::LongDouble) store(Fetch()); + if (classes[index] == ArgumentClass::LongDouble) store(Fetch()); else if (classes[index] == ArgumentClass::Double) store(Fetch()); else store(Fetch()); } @@ -85,8 +85,8 @@ class FormatArguments { private: #ifdef ANYPS5_GUEST_LIFTED template T FetchHost() { - if constexpr (std::is_same_v) { - return LibcDetail::GuestLongDoubleRound(va_arg(host, long double)); + if constexpr (std::is_same_v) { + return LibcDetail::FromWide(static_cast(va_arg(host, long double))); } else if constexpr (std::is_floating_point_v) { return static_cast(va_arg(host, double)); } else if constexpr (std::is_integral_v && sizeof(T) < sizeof(int)) { @@ -110,7 +110,7 @@ class FormatArguments { address = args.overflow_arg_area; args.overflow_arg_area = static_cast(args.overflow_arg_area) + 8; } - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v) { const auto aligned = (reinterpret_cast(args.overflow_arg_area) + 15) & ~std::uintptr_t(15); address = reinterpret_cast(aligned); args.overflow_arg_area = reinterpret_cast(aligned + 16); @@ -124,8 +124,8 @@ class FormatArguments { args.overflow_arg_area = static_cast(args.overflow_arg_area) + 8; } } - if constexpr (std::is_same_v) { - return GuestLongDoubleLoad(address); + if constexpr (std::is_same_v) { + return LibcDetail::GuestLongDoubleLoad(address); } else { T value; std::memcpy(&value, address, sizeof(value)); @@ -315,11 +315,11 @@ inline std::string HexFloat(const std::string& spec, char conversion, double val return HexDigits(spec, conversion, (bits >> 63) != 0, lead, fraction, 13, exponent, maximumLength); } -inline std::string HexFloat(const std::string& spec, char conversion, long double value, std::size_t maximumLength = INT_MAX) { - if (value == 0) return HexDigits(spec, conversion, std::signbit(value), 0, 0, 16, 0, maximumLength); +inline std::string HexFloat(const std::string& spec, char conversion, LibcDetail::GuestLongDouble value, std::size_t maximumLength = INT_MAX) { + if (value == 0) return HexDigits(spec, conversion, LibcDetail::SignBit(value), 0, 0, 16, 0, maximumLength); int exponent = 0; - const auto mantissa = static_cast(std::ldexp(std::frexp(std::fabs(value), &exponent), 64)); - return HexDigits(spec, conversion, std::signbit(value), 1, mantissa << 1, 16, exponent - 1, maximumLength); + const auto mantissa = LibcDetail::NormalizedSignificand(value, &exponent); + return HexDigits(spec, conversion, LibcDetail::SignBit(value), 1, mantissa << 1, 16, exponent - 1, maximumLength); } struct DecimalDigits { @@ -346,11 +346,11 @@ inline std::string DecimalChunks(std::vector words) { return text; } -inline DecimalDigits ConsoleDecimalDigits(long double value) { +inline DecimalDigits ConsoleDecimalDigits(LibcDetail::GuestLongDouble value) { DecimalDigits result; if (value == 0) return result; int binary = 0; - const auto mantissa = static_cast(std::ldexp(std::frexp(std::fabs(value), &binary), 64)); + const auto mantissa = LibcDetail::NormalizedSignificand(value, &binary); const long long estimate = static_cast(binary) * 30103 / 100000; const long long last = (estimate >= 0 ? estimate / 4 : -((3 - estimate) / 4)) * 4 - 20; result.tiesDown = estimate >= 8; @@ -447,7 +447,7 @@ inline char DigitAt(const DecimalDigits& value, long long position) { return value.digits[static_cast(index)]; } -inline std::string DecimalFloat(const std::string& spec, char conversion, long double value, std::size_t maximumLength = INT_MAX) { +inline std::string DecimalFloat(const std::string& spec, char conversion, LibcDetail::GuestLongDouble value, std::size_t maximumLength = INT_MAX) { const FloatSpec parsed = ParseFloatSpec(spec); const DecimalDigits exact = ConsoleDecimalDigits(value); const bool upper = conversion == 'E' || conversion == 'G'; @@ -470,7 +470,7 @@ inline std::string DecimalFloat(const std::string& spec, char conversion, long d while (needed > 0 && DigitAt(rounded, (exponential ? exponent : 0) - needed) == '0') --needed; precision = std::max(0LL, std::min(precision, needed)); } - const std::string prefix = std::signbit(value) ? "-" : parsed.plus ? "+" : parsed.space ? " " : ""; + const std::string prefix = LibcDetail::SignBit(value) ? "-" : parsed.plus ? "+" : parsed.space ? " " : ""; const bool point = precision > 0 || parsed.alternate; const std::string magnitude = std::to_string(exponent < 0 ? -exponent : exponent); const long long whole = exponential || exponent < 0 ? 1 : exponent + 1; @@ -694,15 +694,15 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo output.Value(spec + "ll" + conversion, value); } else if (std::strchr("aAeEfFgG", conversion)) { if (length == "L") { - const long double value = args.Next(); - if (std::isnan(value)) { - output.Value(PaddedStringSpec(spec), NanText(spec, std::signbit(value), conversion < 'a').c_str()); - } else if (std::isfinite(value)) { + const LibcDetail::GuestLongDouble value = args.Next(); + if (LibcDetail::IsNan(value)) { + output.Value(PaddedStringSpec(spec), NanText(spec, LibcDetail::SignBit(value), conversion < 'a').c_str()); + } else if (LibcDetail::IsFinite(value)) { const auto remaining = static_cast(INT_MAX - output.Count()); const std::string text = conversion == 'a' || conversion == 'A' ? HexFloat(spec, conversion, value, remaining) : DecimalFloat(spec, conversion, value, remaining); output.Append(text.data(), text.size()); } else { - output.Value(spec + "L" + conversion, value); + output.Value(spec + conversion, static_cast(value)); } } else { if (!length.empty() && length != "l") throw std::invalid_argument("Invalid floating length"); diff --git a/core/libs/prx/libc/src/DinkumwareInternals.cpp b/core/libs/prx/libc/src/DinkumwareInternals.cpp index 518156c0cd25..72c2555175c2 100644 --- a/core/libs/prx/libc/src/DinkumwareInternals.cpp +++ b/core/libs/prx/libc/src/DinkumwareInternals.cpp @@ -24,9 +24,9 @@ constexpr int InvalidArgument = 22; constexpr int DomainError = 33; constexpr int RangeError = 34; -constexpr long double LogPolynomial[] = { - 0x87f45b3375e0c9edp-66L, 0x87a58c55129af2f8p-66L, 0x9d90cfd7547e4ed2p-66L, 0xba2e6ba9488d3d0cp-66L, - 0xe38e393a60e2888bp-66L, 0x924924920646cc89p-65L, 0xcccccccccd00c2ebp-65L, 0xaaaaaaaaaaaaa334p-64L, +constexpr LibcDetail::GuestLongDouble LogPolynomial[] = { + APS5_GUEST_LONG_DOUBLE(0x87f45b3375e0c9edp-66L, 0x3ffc, 0x87f45b3375e0c9ed), APS5_GUEST_LONG_DOUBLE(0x87a58c55129af2f8p-66L, 0x3ffc, 0x87a58c55129af2f8), APS5_GUEST_LONG_DOUBLE(0x9d90cfd7547e4ed2p-66L, 0x3ffc, 0x9d90cfd7547e4ed2), APS5_GUEST_LONG_DOUBLE(0xba2e6ba9488d3d0cp-66L, 0x3ffc, 0xba2e6ba9488d3d0c), + APS5_GUEST_LONG_DOUBLE(0xe38e393a60e2888bp-66L, 0x3ffc, 0xe38e393a60e2888b), APS5_GUEST_LONG_DOUBLE(0x924924920646cc89p-65L, 0x3ffd, 0x924924920646cc89), APS5_GUEST_LONG_DOUBLE(0xcccccccccd00c2ebp-65L, 0x3ffd, 0xcccccccccd00c2eb), APS5_GUEST_LONG_DOUBLE(0xaaaaaaaaaaaaa334p-64L, 0x3ffe, 0xaaaaaaaaaaaaa334), }; bool Hexadecimal(const char* text) { @@ -62,7 +62,7 @@ TFloat ScaleHexadecimal(TFloat value, std::int64_t power) { mantissa = (bits & (Hidden - 1)) | Hidden; exponent = (bits >> (Precision - 1)) & MaximumExponent; } else { - static_assert(sizeof(TFloat) == sizeof(long double) && std::endian::native == std::endian::little); + static_assert(sizeof(TFloat) == LibcDetail::GuestLongDoubleBytes && std::endian::native == std::endian::little); const auto image = LibcDetail::GuestLongDoubleImage(value); std::uint16_t encoded; std::memcpy(&mantissa, image.data(), sizeof(mantissa)); @@ -155,7 +155,7 @@ std::optional UnscaledHexadecimal(const char* text, char** end) { } } if (end != nullptr) *end = const_cast(cursor); - if (digits.empty()) return std::copysign(static_cast(0), negative ? static_cast(-1) : static_cast(1)); + if (digits.empty()) return LibcDetail::CopySign(static_cast(0), negative ? static_cast(-1) : static_cast(1)); exponent += adjustment; if (carry == 0) while (digits.back() == '0') { digits.pop_back(); exponent += 4; } std::uint64_t high = 0; @@ -170,7 +170,7 @@ std::optional UnscaledHexadecimal(const char* text, char** end) { const auto top = static_cast((high << 8u) | (low >> 56u)); const auto middle = static_cast((low >> 28u) & 0xfffffffu); const auto bottom = static_cast(low & 0xfffffffu) + carry; - constexpr TFloat Radix = static_cast(0x1p28L); + constexpr TFloat Radix = static_cast(0x1p28); constexpr unsigned LimbBits = std::numeric_limits::digits / 2; TFloat limbs[3]{}; const auto add = [&](TFloat word) { @@ -201,7 +201,7 @@ std::optional UnscaledHexadecimal(const char* text, char** end) { for (int index = 2; index >= 0; --index) if (limbs[index] != 0) { if (value == 0) value = limbs[index]; else value += limbs[index]; } } if (exponent != 0) value = ScaleHexadecimal(value, exponent); - return std::copysign(value, negative ? static_cast(-1) : static_cast(1)); + return LibcDetail::CopySign(value, negative ? static_cast(-1) : static_cast(1)); } template @@ -227,8 +227,8 @@ TFloat ScaledFloat(const char* text, char** end, std::int64_t pten, int* error, guestErrno = saved; return value; } - const bool overflowed = std::isinf(value) && guestErrno == RangeError; - if (pten != 0 && stop != text && (std::isfinite(value) || overflowed)) { + const bool overflowed = LibcDetail::IsInf(value) && guestErrno == RangeError; + if (pten != 0 && stop != text && (LibcDetail::IsFinite(value) || overflowed)) { if (Hexadecimal(text)) NotImplemented_nid_no_patch(function); std::string number(text, static_cast(stop)); long long exponent = 0; @@ -243,7 +243,7 @@ TFloat ScaledFloat(const char* text, char** end, std::int64_t pten, int* error, guestErrno = 0; value = parse(number.c_str(), nullptr); } - if (guestErrno == RangeError && (value == 0 || std::isinf(value))) { + if (guestErrno == RangeError && (value == 0 || LibcDetail::IsInf(value))) { if (error != nullptr) *error = 1; } else if (guestErrno == 0 || guestErrno == RangeError) { guestErrno = saved; @@ -344,8 +344,8 @@ float APS5_VABI _Stofx_nid_postfix(const char* text, char** end, std::int64_t pt return ScaledFloat(text, end, pten, error, strtof_nid_postfix, "_Stofx (hexadecimal text with a power-of-ten scale)"); } -long double APS5_VABI _Stoldx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error) { - return ScaledFloat(text, end, pten, error, strtold_nid_postfix, "_Stoldx (hexadecimal text with a power-of-ten scale)"); +LibcDetail::GuestLongDouble APS5_VABI _Stoldx_nid_postfix(const char* text, char** end, std::int64_t pten, int* error) { + return ScaledFloat(text, end, pten, error, strtold_nid_postfix, "_Stoldx (hexadecimal text with a power-of-ten scale)"); } double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten) { @@ -356,7 +356,7 @@ float APS5_VABI _Stof_nid_postfix(const char* text, char** end, std::int64_t pte return _Stofx_nid_postfix(text, end, pten, nullptr); } -long double APS5_VABI _Stold_nid_postfix(const char* text, char** end, std::int64_t pten) { +LibcDetail::GuestLongDouble APS5_VABI _Stold_nid_postfix(const char* text, char** end, std::int64_t pten) { return _Stoldx_nid_postfix(text, end, pten, nullptr); } @@ -589,7 +589,7 @@ float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant) { return quadrant != 0 ? cosf_nid_postfix(x) : sinf_nid_postfix(x); } -long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant) { +LibcDetail::GuestLongDouble APS5_VABI _LSin_nid_postfix(LibcDetail::GuestLongDouble x, unsigned int quadrant) { return quadrant != 0 ? cosl_nid_postfix(x) : sinl_nid_postfix(x); } @@ -619,7 +619,7 @@ float APS5_VABI _FLog_nid_postfix(float x, int base) { return base == 0 ? logf_nid_postfix(x) : log2f_nid_postfix(x); } -long double APS5_VABI _LLog_nid_postfix(long double x, int base) { +LibcDetail::GuestLongDouble APS5_VABI _LLog_nid_postfix(LibcDetail::GuestLongDouble x, int base) { const auto image = LibcDetail::GuestLongDoubleImage(x); std::uint64_t mantissa; std::uint16_t signExponent; @@ -632,12 +632,12 @@ long double APS5_VABI _LLog_nid_postfix(long double x, int base) { } else if (mantissa == 0) { std::feraiseexcept(FE_DIVBYZERO); *__error_nid_postfix() = RangeError; - return -std::numeric_limits::infinity(); + return -std::numeric_limits::infinity(); } if (negative) { std::feraiseexcept(FE_INVALID); *__error_nid_postfix() = DomainError; - return std::numeric_limits::quiet_NaN(); + return std::numeric_limits::quiet_NaN(); } const int shift = std::countl_zero(mantissa); int scale = std::max(exponent, 1) - shift - 0x3ffe; @@ -646,29 +646,29 @@ long double APS5_VABI _LLog_nid_postfix(long double x, int base) { std::array fractionImage{}; std::memcpy(fractionImage.data(), &normalized, sizeof(normalized)); std::memcpy(fractionImage.data() + sizeof(normalized), &halfExponent, sizeof(halfExponent)); - long double fraction = LibcDetail::GuestLongDoubleLoad(fractionImage.data()); - if (fraction < 0xb504f333f9de6484p-64L) { + LibcDetail::GuestLongDouble fraction = LibcDetail::GuestLongDoubleLoad(fractionImage.data()); + if (fraction < APS5_GUEST_LONG_DOUBLE(0xb504f333f9de6484p-64L, 0x3ffe, 0xb504f333f9de6484)) { fraction = fraction + fraction; --scale; } - const long double y = fraction - 1.0L; - const long double z = y / (fraction + 1.0L); - const long double w = z * z; - long double p = LogPolynomial[0]; + const LibcDetail::GuestLongDouble y = fraction - 1; + const LibcDetail::GuestLongDouble z = y / (fraction + 1); + const LibcDetail::GuestLongDouble w = z * z; + LibcDetail::GuestLongDouble p = LogPolynomial[0]; for (std::size_t index = 1; index < std::size(LogPolynomial); ++index) p = p * w + LogPolynomial[index]; - const long double q = z * (y - p * w); - const long double e = scale; - if (base == 0) return (y - (q + -0xfbe8e7bcd5e4f1dap-87L * e)) + e * 0x1.62e42cp-1f; - if (base > 0) return ((0xa937287195355babp-89L * y - (q * 0xde5bd8a937287195p-65L + -0xa13ef3fde623e256p-87L * e)) + y * 0x1.bcb7bp-2f) + e * 0x1.34413p-2f; - return ((q * -0xb8aa3b295c17f0bcp-63L + 0xa5705fc2eefa1ffbp-89L * y) + y * 0x1.715476p+0f) + e; + const LibcDetail::GuestLongDouble q = z * (y - p * w); + const LibcDetail::GuestLongDouble e = scale; + if (base == 0) return (y - (q + APS5_GUEST_LONG_DOUBLE(-0xfbe8e7bcd5e4f1dap-87L, 0xbfe7, 0xfbe8e7bcd5e4f1da) * e)) + e * 0x1.62e42cp-1f; + if (base > 0) return ((APS5_GUEST_LONG_DOUBLE(0xa937287195355babp-89L, 0x3fe5, 0xa937287195355bab) * y - (q * APS5_GUEST_LONG_DOUBLE(0xde5bd8a937287195p-65L, 0x3ffd, 0xde5bd8a937287195) + APS5_GUEST_LONG_DOUBLE(-0xa13ef3fde623e256p-87L, 0xbfe7, 0xa13ef3fde623e256) * e)) + y * 0x1.bcb7bp-2f) + e * 0x1.34413p-2f; + return ((q * APS5_GUEST_LONG_DOUBLE(-0xb8aa3b295c17f0bcp-63L, 0xbfff, 0xb8aa3b295c17f0bc) + APS5_GUEST_LONG_DOUBLE(0xa5705fc2eefa1ffbp-89L, 0x3fe5, 0xa5705fc2eefa1ffb) * y) + y * 0x1.715476p+0f) + e; } -long double APS5_VABI _LDivide_nid_postfix(long double x, long double y) { +LibcDetail::GuestLongDouble APS5_VABI _LDivide_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y) { return x / y; } -long double APS5_VABI _LRecip_nid_postfix(long double x) { - return 1.0L / x; +LibcDetail::GuestLongDouble APS5_VABI _LRecip_nid_postfix(LibcDetail::GuestLongDouble x) { + return 1 / x; } } diff --git a/core/libs/prx/libc/src/FloatBuiltins.cpp b/core/libs/prx/libc/src/FloatBuiltins.cpp index d698a0c0edac..19a4000bceaa 100644 --- a/core/libs/prx/libc/src/FloatBuiltins.cpp +++ b/core/libs/prx/libc/src/FloatBuiltins.cpp @@ -55,7 +55,7 @@ struct ExtendedBits { int exponent; }; -ExtendedBits DecomposeExtended(long double value) { +ExtendedBits DecomposeExtended(LibcDetail::GuestLongDouble value) { const auto bytes = LibcDetail::GuestLongDoubleImage(value); std::uint64_t mantissa; std::uint16_t signExponent; @@ -64,8 +64,8 @@ ExtendedBits DecomposeExtended(long double value) { return {mantissa, (signExponent & 0x8000u) != 0, static_cast(signExponent & 0x7fffu) - 16383}; } -long double ComposeExtended(bool negative, int biased, std::uint64_t mantissa) { - unsigned char bytes[sizeof(long double)] = {}; +LibcDetail::GuestLongDouble ComposeExtended(bool negative, int biased, std::uint64_t mantissa) { + unsigned char bytes[sizeof(LibcDetail::GuestLongDouble)] = {}; const std::uint16_t signExponent = static_cast((negative ? 0x8000u : 0u) | static_cast(biased)); std::memcpy(bytes, &mantissa, sizeof(mantissa)); std::memcpy(bytes + 8, &signExponent, sizeof(signExponent)); @@ -99,7 +99,7 @@ TResult FixUnsigned(TFloat value) { } template -TResult FixExtendedSigned(long double value) { +TResult FixExtendedSigned(LibcDetail::GuestLongDouble value) { const ExtendedBits parts = DecomposeExtended(value); if (parts.exponent < 0) return 0; TResult magnitude; @@ -116,7 +116,7 @@ TResult FixExtendedSigned(long double value) { } template -TResult FixExtendedUnsigned(long double value) { +TResult FixExtendedUnsigned(LibcDetail::GuestLongDouble value) { const ExtendedBits parts = DecomposeExtended(value); if (parts.negative || parts.exponent < 0) return 0; if constexpr (sizeof(TResult) == 16) { @@ -161,7 +161,7 @@ TFloat RoundToFloat(bool negative, Uint128 magnitude) { return std::bit_cast(static_cast(sign | (static_cast(biased) << Format::SignificandBits) | fraction)); } -long double RoundToExtended(bool negative, Uint128 magnitude) { +LibcDetail::GuestLongDouble RoundToExtended(bool negative, Uint128 magnitude) { if (magnitude == 0) return ComposeExtended(negative, 0, 0); int exponent = HighestBit(magnitude); Uint128 significand; @@ -507,7 +507,9 @@ void DivideComplex(TFloat a, TFloat b, TFloat c, TFloat d, TFloat& real, TFloat& using ComplexFloat = __complex__ float; using ComplexDouble = __complex__ double; +#ifndef ANYPS5_GUEST_LIFTED using ComplexExtended = __complex__ long double; +#endif ComplexFloat MakeComplex(float real, float imaginary) { ComplexFloat result; @@ -523,12 +525,14 @@ ComplexDouble MakeComplex(double real, double imaginary) { return result; } +#ifndef ANYPS5_GUEST_LIFTED ComplexExtended MakeComplex(long double real, long double imaginary) { ComplexExtended result; __real__ result = real; __imag__ result = imaginary; return result; } +#endif } @@ -600,11 +604,11 @@ std::uint32_t APS5_VABI __fixunsdfsi_nid_postfix(double a) { std::uint64_t APS5_VABI __fixunsdfdi_nid_postfix(double a) { return FixUnsigned(a); } Uint128 APS5_VABI __fixunsdfti_nid_postfix(double a) { return FixUnsigned(a); } -std::int64_t APS5_VABI __fixxfdi_nid_postfix(long double a) { return FixExtendedSigned(a); } -Int128 APS5_VABI __fixxfti_nid_postfix(long double a) { return FixExtendedSigned(a); } -std::uint32_t APS5_VABI __fixunsxfsi_nid_postfix(long double a) { return FixExtendedUnsigned(a); } -std::uint64_t APS5_VABI __fixunsxfdi_nid_postfix(long double a) { return FixExtendedUnsigned(a); } -Uint128 APS5_VABI __fixunsxfti_nid_postfix(long double a) { return FixExtendedUnsigned(a); } +std::int64_t APS5_VABI __fixxfdi_nid_postfix(LibcDetail::GuestLongDouble a) { return FixExtendedSigned(a); } +Int128 APS5_VABI __fixxfti_nid_postfix(LibcDetail::GuestLongDouble a) { return FixExtendedSigned(a); } +std::uint32_t APS5_VABI __fixunsxfsi_nid_postfix(LibcDetail::GuestLongDouble a) { return FixExtendedUnsigned(a); } +std::uint64_t APS5_VABI __fixunsxfdi_nid_postfix(LibcDetail::GuestLongDouble a) { return FixExtendedUnsigned(a); } +Uint128 APS5_VABI __fixunsxfti_nid_postfix(LibcDetail::GuestLongDouble a) { return FixExtendedUnsigned(a); } double APS5_VABI __floatsidf_nid_postfix(std::int32_t a) { return static_cast(a); } double APS5_VABI __floatunsidf_nid_postfix(std::uint32_t a) { return static_cast(a); } @@ -612,16 +616,16 @@ float APS5_VABI __floatsisf_nid_postfix(std::int32_t a) { return RoundToFloat(false, a); } double APS5_VABI __floatdidf_nid_postfix(std::int64_t a) { return static_cast(a); } float APS5_VABI __floatdisf_nid_postfix(std::int64_t a) { return static_cast(a); } -long double APS5_VABI __floatdixf_nid_postfix(std::int64_t a) { return static_cast(a); } +LibcDetail::GuestLongDouble APS5_VABI __floatdixf_nid_postfix(std::int64_t a) { return LibcDetail::GuestLongDouble(a); } double APS5_VABI __floatundidf_nid_postfix(std::uint64_t a) { return static_cast(a); } float APS5_VABI __floatundisf_nid_postfix(std::uint64_t a) { return static_cast(a); } -long double APS5_VABI __floatundixf_nid_postfix(std::uint64_t a) { return static_cast(a); } +LibcDetail::GuestLongDouble APS5_VABI __floatundixf_nid_postfix(std::uint64_t a) { return LibcDetail::GuestLongDouble(a); } double APS5_VABI __floattidf_nid_postfix(Int128 a) { return RoundToFloat(a < 0, Magnitude(a)); } float APS5_VABI __floattisf_nid_postfix(Int128 a) { return RoundToFloat(a < 0, Magnitude(a)); } -long double APS5_VABI __floattixf_nid_postfix(Int128 a) { return RoundToExtended(a < 0, Magnitude(a)); } +LibcDetail::GuestLongDouble APS5_VABI __floattixf_nid_postfix(Int128 a) { return RoundToExtended(a < 0, Magnitude(a)); } double APS5_VABI __floatuntidf_nid_postfix(Uint128 a) { return RoundToFloat(false, a); } float APS5_VABI __floatuntisf_nid_postfix(Uint128 a) { return RoundToFloat(false, a); } -long double APS5_VABI __floatuntixf_nid_postfix(Uint128 a) { return RoundToExtended(false, a); } +LibcDetail::GuestLongDouble APS5_VABI __floatuntixf_nid_postfix(Uint128 a) { return RoundToExtended(false, a); } __complex__ float APS5_VABI __mulsc3_nid_postfix(float a, float b, float c, float d) { float real, imaginary; @@ -635,11 +639,13 @@ __complex__ double APS5_VABI __muldc3_nid_postfix(double a, double b, double c, return MakeComplex(real, imaginary); } +#ifndef ANYPS5_GUEST_LIFTED __complex__ long double APS5_VABI __mulxc3_nid_postfix(long double a, long double b, long double c, long double d) { long double real, imaginary; MultiplyComplex(a, b, c, d, real, imaginary); return MakeComplex(real, imaginary); } +#endif __complex__ float APS5_VABI __divsc3_nid_postfix(float a, float b, float c, float d) { float real, imaginary; @@ -653,15 +659,17 @@ __complex__ double APS5_VABI __divdc3_nid_postfix(double a, double b, double c, return MakeComplex(real, imaginary); } +#ifndef ANYPS5_GUEST_LIFTED __complex__ long double APS5_VABI __divxc3_nid_postfix(long double a, long double b, long double c, long double d) { long double real, imaginary; DivideComplex(a, b, c, d, real, imaginary); return MakeComplex(real, imaginary); } +#endif -long double APS5_VABI __powixf2_nid_postfix(long double base, int exponent) { +LibcDetail::GuestLongDouble APS5_VABI __powixf2_nid_postfix(LibcDetail::GuestLongDouble base, int exponent) { const bool reciprocal = exponent < 0; - long double result = (exponent & 1) != 0 ? base : 1.0L; + LibcDetail::GuestLongDouble result = (exponent & 1) != 0 ? base : LibcDetail::GuestLongDouble(1); for (exponent /= 2; exponent != 0; exponent /= 2) { base *= base; if (exponent & 1) result *= base; diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 826531c634c0..36e343815bf8 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -22,6 +22,8 @@ #include "prx/libc/include/WindowsFormatting.hpp" #include "prx/libc/include/ScanWideField.hpp" +#include "prx/libc/include/ScanFloatField.hpp" +#include "prx/libc/include/GuestLongDouble.hpp" #include "prx/libc/include/ScanQuadLength.hpp" #ifdef _WIN32 @@ -483,6 +485,39 @@ int ScanSource(TSource source, const char* format, bool secure, NextPointer next target = nextPointer(); if (target == nullptr) return EOF; } +#ifdef ANYPS5_GUEST_LIFTED + if (length == "L" && std::strchr("aAeEfFgG", conversion) != nullptr) { + std::string field; + const std::size_t limit = width == 0 ? std::string::npos : width; + while (field.size() < limit) { + const int next = source.Peek(); + if (next == EOF) break; + field.push_back(static_cast(next)); + if (LibcDetail::FloatFit(field) == LibcDetail::Fit::Invalid) { + field.pop_back(); + break; + } + source.Skip(); + } + if (LibcDetail::FloatFit(field) != LibcDetail::Fit::Valid) return finish(!converted && source.Peek() == EOF ? EOF : assigned); + const int savedError = errno; + errno = 0; + const LibcDetail::GuestLongDouble value = LibcDetail::GuestLongDoubleParse(field.c_str(), nullptr); + const bool rangeError = errno == ERANGE; + if constexpr (TSource::Contiguous) { + const bool outOfRange = rangeError && field.find_first_of("xX") == std::string::npos && (LibcDetail::IsInf(value) || value == 0); + errno = outOfRange ? ERANGE : savedError; + } else { + errno = rangeError ? ERANGE : savedError; + } + if (!suppress) { + LibcDetail::GuestLongDoubleStore(target, LibcDetail::IsNan(value) ? LibcDetail::GuestLongDouble::FromBits(0x7fff, 0xc000000000000000ull) : value); + ++assigned; + } + converted = true; + continue; + } +#endif if (conversion == 'c' && length != "l" && (!TSource::Contiguous || !secure)) { const auto narrowWidth = static_cast(width); const unsigned long wanted = secure ? (width == 0 ? 1 : width) : (narrowWidth == 0 || narrowWidth > INT_MAX ? 1 : narrowWidth); diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index e5a01391a836..e8df135a9a91 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -198,14 +198,14 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = const char narrow = static_cast(conversion); const bool hexadecimal = narrow == 'a' || narrow == 'A'; if (length == "L") { - const long double value = args.Next(); - if (std::isnan(value)) { - AppendPadded(out, WideNanText(spec, std::signbit(value), conversion < u'a'), left, width); - } else if (std::isfinite(value)) { + const LibcDetail::GuestLongDouble value = args.Next(); + if (LibcDetail::IsNan(value)) { + AppendPadded(out, WideNanText(spec, LibcDetail::SignBit(value), conversion < u'a'), left, width); + } else if (LibcDetail::IsFinite(value)) { const std::string text = hexadecimal ? LibcDetail::HexFloat(spec, narrow, value) : LibcDetail::DecimalFloat(spec, narrow, value); out.append(text.begin(), text.end()); } else { - AppendNumber(out, spec + "L" + narrow, value); + AppendNumber(out, spec + narrow, static_cast(value)); } } else { if (!length.empty() && length != "l") throw std::invalid_argument("Invalid floating length"); diff --git a/core/libs/prx/libc/src/GuestLongDoubleParse.cpp b/core/libs/prx/libc/src/GuestLongDoubleParse.cpp new file mode 100644 index 000000000000..3ef32433d3db --- /dev/null +++ b/core/libs/prx/libc/src/GuestLongDoubleParse.cpp @@ -0,0 +1,264 @@ +#include "prx/libc/include/GuestLongDouble.hpp" + +#ifdef ANYPS5_GUEST_LIFTED + +#include +#include +#include +#include +#include +#include + +namespace { + +using Natural = std::vector; +using LibcDetail::GuestLongDouble; + +constexpr long long ExponentLimit = 1000000000; +constexpr std::uint64_t Integer = 0x8000000000000000ull; + +void Trim(Natural& value) { + while (!value.empty() && value.back() == 0) value.pop_back(); +} + +void MultiplyAdd(Natural& value, const std::uint32_t factor, const std::uint32_t addend) { + std::uint64_t carry = addend; + for (auto& limb : value) { + const std::uint64_t product = static_cast(limb) * factor + carry; + limb = static_cast(product); + carry = product >> 32; + } + if (carry != 0) value.push_back(static_cast(carry)); +} + +void MultiplyByPowerOfFive(Natural& value, long long exponent) { + for (; exponent >= 13; exponent -= 13) MultiplyAdd(value, 1220703125u, 0); + std::uint32_t rest = 1; + for (; exponent > 0; --exponent) rest *= 5; + MultiplyAdd(value, rest, 0); +} + +std::size_t BitLength(const Natural& value) { + return value.empty() ? 0 : 32 * (value.size() - 1) + static_cast(32 - std::countl_zero(value.back())); +} + +bool Bit(const Natural& value, const std::size_t index) { + const std::size_t limb = index / 32; + return limb < value.size() && ((value[limb] >> (index % 32)) & 1u) != 0; +} + +bool AnyBelow(const Natural& value, const std::size_t count) { + const std::size_t whole = std::min(count / 32, value.size()); + for (std::size_t index = 0; index < whole; ++index) { + if (value[index] != 0) return true; + } + return whole < value.size() && count % 32 != 0 && (value[whole] & ((1u << (count % 32)) - 1)) != 0; +} + +std::uint64_t BitsFrom(const Natural& value, const std::size_t from, const long long count) { + std::uint64_t bits = 0; + for (long long index = 0; index < count; ++index) { + if (Bit(value, from + static_cast(index))) bits |= std::uint64_t {1} << index; + } + return bits; +} + +Natural ShiftedLeft(const Natural& value, const std::size_t bits) { + Natural result(bits / 32, 0); + const unsigned shift = bits % 32; + std::uint32_t carry = 0; + for (const auto limb : value) { + result.push_back(shift == 0 ? limb : (limb << shift) | carry); + carry = shift == 0 ? 0 : limb >> (32 - shift); + } + if (carry != 0) result.push_back(carry); + Trim(result); + return result; +} + +int Compare(const Natural& left, const Natural& right) { + if (left.size() != right.size()) return left.size() < right.size() ? -1 : 1; + for (std::size_t index = left.size(); index-- > 0;) { + if (left[index] != right[index]) return left[index] < right[index] ? -1 : 1; + } + return 0; +} + +void Subtract(Natural& left, const Natural& right) { + std::int64_t borrow = 0; + for (std::size_t index = 0; index < left.size(); ++index) { + std::int64_t difference = static_cast(left[index]) - borrow - (index < right.size() ? right[index] : 0); + borrow = difference < 0 ? 1 : 0; + if (difference < 0) difference += std::int64_t {1} << 32; + left[index] = static_cast(difference); + } + Trim(left); +} + +struct Quotient { + Natural value; + bool inexact; +}; + +Quotient Divide(const Natural& dividend, const Natural& divisor) { + Natural remainder = dividend; + Natural quotient; + const std::size_t dividendBits = BitLength(dividend); + const std::size_t divisorBits = BitLength(divisor); + if (dividendBits >= divisorBits) { + const std::size_t bits = dividendBits - divisorBits + 1; + quotient.assign((bits + 31) / 32, 0); + for (std::size_t shift = bits; shift-- > 0;) { + const Natural scaled = ShiftedLeft(divisor, shift); + if (Compare(remainder, scaled) >= 0) { + Subtract(remainder, scaled); + quotient[shift / 32] |= 1u << (shift % 32); + } + } + Trim(quotient); + } + return {quotient, !remainder.empty()}; +} + +GuestLongDouble Overflow(const bool negative, const unsigned rounding) { + errno = ERANGE; + const auto sign = static_cast(negative ? 0x8000 : 0); + const bool infinite = rounding == 0 || (rounding == 1 && negative) || (rounding == 2 && !negative); + return infinite ? GuestLongDouble::FromBits(sign | 0x7fff, Integer) : GuestLongDouble::FromBits(sign | 0x7ffe, ~std::uint64_t {0}); +} + +GuestLongDouble Round(const bool negative, const Natural& value, const long long scale, const bool sticky) { + const auto rounding = static_cast((GuestCpu::X87Control() >> 10) & 3); + const auto sign = static_cast(negative ? 0x8000 : 0); + const auto length = static_cast(BitLength(value)); + if (length == 0) return GuestLongDouble::FromBits(sign, 0); + const long long top = length - 1 + scale; + const long long precision = top < -16382 ? 64 - (-16382 - top) : 64; + const long long shift = length - precision; + std::uint64_t mantissa = 0; + bool half = false; + bool rest = sticky; + if (shift <= 0) { + mantissa = BitsFrom(value, 0, length) << -shift; + } else { + mantissa = precision > 0 ? BitsFrom(value, static_cast(shift), precision) : 0; + half = Bit(value, static_cast(shift - 1)); + rest = rest || AnyBelow(value, static_cast(shift - 1)); + } + bool up = false; + switch (rounding) { + case 0: up = half && (rest || (mantissa & 1) != 0); break; + case 1: up = negative && (half || rest); break; + case 2: up = !negative && (half || rest); break; + default: break; + } + if (precision < 64) { + if (up) ++mantissa; + if ((mantissa >> 63) != 0) return GuestLongDouble::FromBits(sign | 1, mantissa); + if (half || rest) errno = ERANGE; + return GuestLongDouble::FromBits(sign, mantissa); + } + long long biased = top + 16383; + if (up && ++mantissa == 0) { + mantissa = Integer; + ++biased; + } + if (biased >= 0x7fff) return Overflow(negative, rounding); + return GuestLongDouble::FromBits(static_cast(sign | biased), mantissa); +} + +int DigitValue(const char character, const int radix) { + int value = 36; + if (character >= '0' && character <= '9') value = character - '0'; + else if (character >= 'a' && character <= 'z') value = character - 'a' + 10; + else if (character >= 'A' && character <= 'Z') value = character - 'A' + 10; + return value < radix ? value : -1; +} + +bool StartsWithWord(const char* text, const char* word) { + for (; *word != '\0'; ++text, ++word) { + if (std::tolower(static_cast(*text)) != *word) return false; + } + return true; +} + +} + +LibcDetail::GuestLongDouble LibcDetail::GuestLongDoubleParse(const char* text, char** end) { + const char* cursor = text; + while (std::isspace(static_cast(*cursor))) ++cursor; + const bool negative = *cursor == '-'; + if (*cursor == '+' || *cursor == '-') ++cursor; + const auto sign = static_cast(negative ? 0x8000 : 0); + const auto finish = [end](const char* stop, const GuestLongDouble value) { + if (end != nullptr) *end = const_cast(stop); + return value; + }; + if (StartsWithWord(cursor, "inf")) return finish(cursor + (StartsWithWord(cursor, "infinity") ? 8 : 3), GuestLongDouble::FromBits(sign | 0x7fff, Integer)); + if (StartsWithWord(cursor, "nan")) { + const char* stop = cursor + 3; + if (*stop == '(') { + const char* close = stop + 1; + while (std::isalnum(static_cast(*close)) || *close == '_') ++close; + if (*close == ')') stop = close + 1; + } + return finish(stop, GuestLongDouble::FromBits(sign | 0x7fff, 0xc000000000000000ull)); + } + const bool hexadecimal = cursor[0] == '0' && (cursor[1] == 'x' || cursor[1] == 'X') && + (DigitValue(cursor[2], 16) >= 0 || (cursor[2] == '.' && DigitValue(cursor[3], 16) >= 0)); + const int radix = hexadecimal ? 16 : 10; + Natural value; + long long significant = 0; + long long fraction = 0; + bool any = false; + bool point = false; + const char* scan = hexadecimal ? cursor + 2 : cursor; + for (;; ++scan) { + if (*scan == '.' && !point) { + point = true; + continue; + } + const int digit = DigitValue(*scan, radix); + if (digit < 0) break; + any = true; + if (point && fraction < ExponentLimit) ++fraction; + if (value.empty() && digit == 0) continue; + MultiplyAdd(value, static_cast(radix), static_cast(digit)); + ++significant; + } + if (!any) return finish(text, GuestLongDouble {}); + long long exponent = 0; + if (std::tolower(static_cast(*scan)) == (hexadecimal ? 'p' : 'e')) { + const char* exponentText = scan + 1; + const bool minus = *exponentText == '-'; + if (*exponentText == '+' || *exponentText == '-') ++exponentText; + if (std::isdigit(static_cast(*exponentText))) { + for (; std::isdigit(static_cast(*exponentText)); ++exponentText) exponent = std::min(exponent * 10 + (*exponentText - '0'), ExponentLimit); + if (minus) exponent = -exponent; + scan = exponentText; + } + } + if (value.empty()) return finish(scan, GuestLongDouble::FromBits(sign, 0)); + if (hexadecimal) { + const long long binary = exponent - 4 * fraction; + const long long top = static_cast(BitLength(value)) - 1 + binary; + if (top > 16384) return finish(scan, Overflow(negative, static_cast((GuestCpu::X87Control() >> 10) & 3))); + if (top < -16500) return finish(scan, Round(negative, Natural {1}, -20000, true)); + return finish(scan, Round(negative, value, binary, false)); + } + const long long scale = exponent - fraction; + const long long magnitude = significant - 1 + scale; + if (magnitude > 4933) return finish(scan, Overflow(negative, static_cast((GuestCpu::X87Control() >> 10) & 3))); + if (magnitude < -4952) return finish(scan, Round(negative, Natural {1}, -20000, true)); + if (scale >= 0) { + MultiplyByPowerOfFive(value, scale); + return finish(scan, Round(negative, value, scale, false)); + } + Natural power {1}; + MultiplyByPowerOfFive(power, -scale); + const long long widen = std::max(0ll, static_cast(BitLength(power)) - static_cast(BitLength(value)) + 66); + const Quotient quotient = Divide(ShiftedLeft(value, static_cast(widen)), power); + return finish(scan, Round(negative, quotient.value, scale - widen, quotient.inexact)); +} + +#endif diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 7c80789c5b7b..2e0458696642 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -181,11 +181,13 @@ #include "prx/libc/include/GuestCpu.hpp" #include "prx/libc/include/SonyCrt.hpp" +using LibcDetail::GuestLongDouble; + namespace { template std::int64_t RoundToInt64(TFloat value) { - const TFloat rounded = std::round(value); + const TFloat rounded = LibcDetail::RoundIntegral(value, LibcDetail::IntegralRounding::HalfAwayFromZero); if (!(rounded >= static_cast(INT64_MIN) && rounded < -static_cast(INT64_MIN))) { std::feraiseexcept(FE_INVALID); return INT64_MIN; @@ -303,7 +305,7 @@ float AtanhfLog1p(float x) { return kFloat * Ln2High - ((halfSquare - (s * (halfSquare + r) + (kFloat * Ln2Low + c))) - f); } -static_assert(std::numeric_limits::digits >= 64, "host long double holds the guest x87 80-bit format"); +static_assert(std::numeric_limits::digits >= 64, "the guest GuestLongDouble holds the x87 80-bit format"); struct ExtendedParts { std::uint64_t mantissa; @@ -312,22 +314,22 @@ struct ExtendedParts { template TValue Hypot3(TValue x, TValue y, TValue z, THypot hypot) { - if (std::isnan(x)) return x; - if (std::isnan(y)) return y; - if (std::isnan(z)) return z; - if (std::isinf(x) || std::isinf(y) || std::isinf(z)) return std::numeric_limits::infinity(); + if (LibcDetail::IsNan(x)) return x; + if (LibcDetail::IsNan(y)) return y; + if (LibcDetail::IsNan(z)) return z; + if (LibcDetail::IsInf(x) || LibcDetail::IsInf(y) || LibcDetail::IsInf(z)) return std::numeric_limits::infinity(); if (x == 0) return hypot(y, z); if (y == 0) return hypot(x, z); if (z == 0) return hypot(x, y); - x = std::fabs(x); - y = std::fabs(y); - z = std::fabs(z); + x = LibcDetail::Fabs(x); + y = LibcDetail::Fabs(y); + z = LibcDetail::Fabs(z); if (z >= x && z >= y) return hypot(z, hypot(x, y)); if (y >= x && y >= z) return hypot(y, hypot(x, z)); return hypot(x, hypot(y, z)); } -ExtendedParts SplitExtended(long double value) { +ExtendedParts SplitExtended(GuestLongDouble value) { const auto bytes = LibcDetail::GuestLongDoubleImage(value); ExtendedParts parts; std::memcpy(&parts.mantissa, bytes.data(), sizeof(parts.mantissa)); @@ -335,47 +337,55 @@ ExtendedParts SplitExtended(long double value) { return parts; } -long double JoinExtended(std::uint64_t mantissa, std::uint16_t signExponent) { - unsigned char bytes[sizeof(long double)] = {}; +GuestLongDouble JoinExtended(std::uint64_t mantissa, std::uint16_t signExponent) { + unsigned char bytes[sizeof(GuestLongDouble)] = {}; std::memcpy(bytes, &mantissa, sizeof(mantissa)); std::memcpy(bytes + sizeof(mantissa), &signExponent, sizeof(signExponent)); - long double value; + GuestLongDouble value; std::memcpy(&value, bytes, sizeof(value)); return value; } -long double ClearLowMantissaWord(long double value) { +GuestLongDouble ClearLowMantissaWord(GuestLongDouble value) { auto bytes = LibcDetail::GuestLongDoubleImage(value); std::memset(bytes.data(), 0, sizeof(std::uint32_t)); return LibcDetail::GuestLongDoubleLoad(bytes.data()); } -long double SumAtCallerPrecision(long double high, long double low) { +GuestLongDouble SumAtCallerPrecision(GuestLongDouble high, GuestLongDouble low) { +#ifdef ANYPS5_GUEST_LIFTED + return high + low; +#else volatile long double sum = high; return sum + low; +#endif } -constexpr long double AsinNumeratorCoefficients[] = { - 1.66666666666666666631e-01L, -4.16313987993683104320e-01L, 3.69068046323246813704e-01L, -1.36213932016738603108e-01L, - 1.78324189708471965733e-02L, -2.19216428382605211588e-04L, -7.10526623669075243183e-06L, +constexpr GuestLongDouble AsinNumeratorCoefficients[] = { + APS5_GUEST_LONG_DOUBLE(1.66666666666666666631e-01L, 0x3ffc, 0xaaaaaaaaaaaaaaa8), APS5_GUEST_LONG_DOUBLE(-4.16313987993683104320e-01L, 0xbffd, 0xd5271b6699b48bfa), APS5_GUEST_LONG_DOUBLE(3.69068046323246813704e-01L, 0x3ffd, 0xbcf67ca9e9f669cf), APS5_GUEST_LONG_DOUBLE(-1.36213932016738603108e-01L, 0xbffc, 0x8b7baa3d15f9830d), + APS5_GUEST_LONG_DOUBLE(1.78324189708471965733e-02L, 0x3ff9, 0x92154b093a3bff1c), APS5_GUEST_LONG_DOUBLE(-2.19216428382605211588e-04L, 0xbff2, 0xe5dd76401964508c), APS5_GUEST_LONG_DOUBLE(-7.10526623669075243183e-06L, 0xbfed, 0xee69c5b0fdb76951), }; -constexpr long double AsinDenominatorCoefficients[] = { - -2.94788392796209867269e+00L, 3.27309890266528636716e+00L, -1.68285799854822427013e+00L, 3.90699412641738801874e-01L, - -3.14365703596053263322e-02L, +constexpr GuestLongDouble AsinDenominatorCoefficients[] = { + APS5_GUEST_LONG_DOUBLE(-2.94788392796209867269e+00L, 0xc000, 0xbcaa2159c01436a0), APS5_GUEST_LONG_DOUBLE(3.27309890266528636716e+00L, 0x4000, 0xd17a73d1e1564c29), APS5_GUEST_LONG_DOUBLE(-1.68285799854822427013e+00L, 0xbfff, 0xd767e411c9cf4c2c), APS5_GUEST_LONG_DOUBLE(3.90699412641738801874e-01L, 0x3ffd, 0xc809c0dfb9b0d0b7), + APS5_GUEST_LONG_DOUBLE(-3.14365703596053263322e-02L, 0xbffa, 0x80c3a2197c8ced57), }; -constexpr long double HalfPiHigh = 1.57079632679489661926e+00L; -constexpr long double HalfPiLow = -2.50827880633416601173e-20L; -constexpr long double QuarterPiHigh = 7.85398163397448309628e-01L; +constexpr GuestLongDouble HalfPiHigh = APS5_GUEST_LONG_DOUBLE(1.57079632679489661926e+00L, 0x3fff, 0xc90fdaa22168c235); +constexpr GuestLongDouble HalfPiLow = APS5_GUEST_LONG_DOUBLE(-2.50827880633416601173e-20L, 0xbfbd, 0xece675d1fc8f8cbb); +constexpr GuestLongDouble QuarterPiHigh = APS5_GUEST_LONG_DOUBLE(7.85398163397448309628e-01L, 0x3ffe, 0xc90fdaa22168c235); +#ifdef ANYPS5_GUEST_LIFTED +constexpr GuestLongDouble Pi = APS5_GUEST_LONG_DOUBLE(3.14159265358979323846264338327950280e+00L, 0x4000, 0xc90fdaa22168c235); +#else const volatile long double Pi = 3.14159265358979323846264338327950280e+00L; +#endif -long double AsinNumerator(long double t) { +GuestLongDouble AsinNumerator(GuestLongDouble t) { const auto& p = AsinNumeratorCoefficients; return t * (p[0] + t * (p[1] + t * (p[2] + t * (p[3] + t * (p[4] + t * (p[5] + t * p[6])))))); } -long double AsinDenominator(long double t) { +GuestLongDouble AsinDenominator(GuestLongDouble t) { const auto& q = AsinDenominatorCoefficients; - return 1.0L + t * (q[0] + t * (q[1] + t * (q[2] + t * (q[3] + t * q[4])))); + return 1.0 + t * (q[0] + t * (q[1] + t * (q[2] + t * (q[3] + t * q[4])))); } template @@ -404,8 +414,8 @@ T GuestFmin(T x, T y) { return y; if (NanWithoutException(y)) return x; - if (std::signbit(x) != std::signbit(y)) - return std::signbit(x) ? x : y; + if (LibcDetail::SignBit(x) != LibcDetail::SignBit(y)) + return LibcDetail::SignBit(x) ? x : y; return x < y ? x : y; } @@ -415,8 +425,8 @@ T GuestFmax(T x, T y) { return y; if (NanWithoutException(y)) return x; - if (std::signbit(x) != std::signbit(y)) - return std::signbit(x) ? y : x; + if (LibcDetail::SignBit(x) != LibcDetail::SignBit(y)) + return LibcDetail::SignBit(x) ? y : x; return x < y ? y : x; } @@ -895,7 +905,7 @@ float APS5_VABI scalbnf_nid_postfix(float x, int exponent) { errno = saved_errno; return result; } -long double APS5_VABI scalbnl_nid_postfix(long double x, int exponent) { return std::scalbn(x, exponent); } +GuestLongDouble APS5_VABI scalbnl_nid_postfix(GuestLongDouble x, int exponent) { return LibcDetail::Scalbn(x, exponent); } float APS5_VABI significandf_nid_postfix(float x) { return x == 0.0f || !std::isfinite(x) ? x : std::scalbn(x, -std::ilogb(x)); } double APS5_VABI significand_nid_postfix(double x) { return x == 0.0 || !std::isfinite(x) ? x : std::scalbn(x, -std::ilogb(x)); } double APS5_VABI frexp_nid_postfix(double x, int* exponent) { @@ -935,12 +945,12 @@ std::int64_t APS5_VABI lround_nid_postfix(double x) { return RoundToInt64(x); } std::int64_t APS5_VABI lroundf_nid_postfix(float x) { return RoundToInt64(x); } std::int64_t APS5_VABI llround_nid_postfix(double x) { return RoundToInt64(x); } std::int64_t APS5_VABI llroundf_nid_postfix(float x) { return RoundToInt64(x); } -std::int64_t APS5_VABI llroundl_nid_postfix(long double x) { return RoundToInt64(x); } +std::int64_t APS5_VABI llroundl_nid_postfix(GuestLongDouble x) { return RoundToInt64(x); } int APS5_VABI __isfinitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0; } int APS5_VABI __isnormal_nid_postfix(double x) { return std::isnormal(x) ? 1 : 0; } int APS5_VABI __isnormalf_nid_postfix(float x) { return std::isnormal(x) ? 1 : 0; } int APS5_VABI __isinff_nid_postfix(float x) { return std::isinf(x) ? 1 : 0; } -long double APS5_VABI nearbyintl_nid_postfix(long double x) { return std::nearbyint(x); } +GuestLongDouble APS5_VABI nearbyintl_nid_postfix(GuestLongDouble x) { return LibcDetail::NearbyInt(x); } double APS5_VABI cbrt_nid_postfix(double x) { const std::uint32_t B1 = 715094163; @@ -990,7 +1000,7 @@ double APS5_VABI sqrt_nid_postfix(double x) { return std::sqrt(x); } float APS5_VABI sqrtf_nid_postfix(float x) { return std::sqrt(x); } double APS5_VABI fabs_nid_postfix(double x) { return std::fabs(x); } float APS5_VABI fabsf_nid_postfix(float x) { return std::fabs(x); } -long double APS5_VABI fabsl_nid_postfix(long double x) { return std::fabs(x); } +GuestLongDouble APS5_VABI fabsl_nid_postfix(GuestLongDouble x) { return LibcDetail::Fabs(x); } double APS5_VABI copysign_nid_postfix(double x, double y) { return std::copysign(x, y); } @@ -1006,10 +1016,10 @@ double APS5_VABI fma_nid_postfix(double x, double y, double z) { return std::fma float APS5_VABI fmaf_nid_postfix(float x, float y, float z) { return std::fma(x, y, z); } double APS5_VABI fmin_nid_postfix(double x, double y) { return GuestFmin(x, y); } float APS5_VABI fminf_nid_postfix(float x, float y) { return GuestFmin(x, y); } -long double APS5_VABI fminl_nid_postfix(long double x, long double y) { return GuestFmin(x, y); } +GuestLongDouble APS5_VABI fminl_nid_postfix(GuestLongDouble x, GuestLongDouble y) { return GuestFmin(x, y); } double APS5_VABI fmax_nid_postfix(double x, double y) { return GuestFmax(x, y); } float APS5_VABI fmaxf_nid_postfix(float x, float y) { return GuestFmax(x, y); } -long double APS5_VABI fmaxl_nid_postfix(long double x, long double y) { return GuestFmax(x, y); } +GuestLongDouble APS5_VABI fmaxl_nid_postfix(GuestLongDouble x, GuestLongDouble y) { return GuestFmax(x, y); } double APS5_VABI fdim_nid_postfix(double x, double y) { if (std::isnan(x) || std::isnan(y)) return std::fdim(x, y); const double difference = x - y; @@ -1031,40 +1041,40 @@ float APS5_VABI fdimf_nid_postfix(float x, float y) { * ==================================================== */ /* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_acosl.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_acosl.c */ -long double APS5_VABI acosl_nid_postfix(long double x) { +GuestLongDouble APS5_VABI acosl_nid_postfix(GuestLongDouble x) { const ExtendedParts parts = SplitExtended(x); const int exponent = parts.signExponent & 0x7fff; const bool negative = (parts.signExponent & 0x8000) != 0; if (exponent >= 0x3fff) { - if (exponent == 0x3fff && (parts.mantissa & 0x7fffffffffffffffull) == 0) return negative ? SumAtCallerPrecision(Pi, 2.0L * HalfPiLow) : 0.0L; + if (exponent == 0x3fff && (parts.mantissa & 0x7fffffffffffffffull) == 0) return negative ? SumAtCallerPrecision(Pi, 2 * HalfPiLow) : GuestLongDouble(0); std::feraiseexcept(FE_INVALID); errno = EDOM; return (x - x) / (x - x); } if (exponent < 0x3ffe) { if (exponent < 0x3fff - 65) return SumAtCallerPrecision(HalfPiHigh, HalfPiLow); - const long double z = x * x; - const long double r = AsinNumerator(z) / AsinDenominator(z); + const GuestLongDouble z = x * x; + const GuestLongDouble r = AsinNumerator(z) / AsinDenominator(z); return HalfPiHigh - (x - (HalfPiLow - x * r)); } if (negative) { - const long double z = (1.0L + x) * 0.5L; - const long double s = std::sqrt(z); - const long double r = AsinNumerator(z) / AsinDenominator(z); - return Pi - 2.0L * (s + (r * s - HalfPiLow)); - } - const long double z = (1.0L - x) * 0.5L; - const long double s = std::sqrt(z); - const long double high = ClearLowMantissaWord(s); - const long double correction = (z - high * high) / (s + high); - const long double r = AsinNumerator(z) / AsinDenominator(z); - return 2.0L * (high + (r * s + correction)); -} -long double APS5_VABI acoshl_nid_postfix(long double x) { return std::acosh(x); } -long double APS5_VABI asinhl_nid_postfix(long double x) { return std::asinh(x); } -long double APS5_VABI atanhl_nid_postfix(long double x) { return std::atanh(x); } -long double APS5_VABI atan2l_nid_postfix(long double y, long double x) { return std::atan2(y, x); } -long double APS5_VABI cbrtl_nid_postfix(long double x) { + const GuestLongDouble z = (1 + x) * 0.5; + const GuestLongDouble s = LibcDetail::Sqrt(z); + const GuestLongDouble r = AsinNumerator(z) / AsinDenominator(z); + return Pi - 2 * (s + (r * s - HalfPiLow)); + } + const GuestLongDouble z = (1 - x) * 0.5; + const GuestLongDouble s = LibcDetail::Sqrt(z); + const GuestLongDouble high = ClearLowMantissaWord(s); + const GuestLongDouble correction = (z - high * high) / (s + high); + const GuestLongDouble r = AsinNumerator(z) / AsinDenominator(z); + return 2 * (high + (r * s + correction)); +} +GuestLongDouble APS5_VABI acoshl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::acosh(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI asinhl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::asinh(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI atanhl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::atanh(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI atan2l_nid_postfix(GuestLongDouble y, GuestLongDouble x) { return LibcDetail::FromWide(std::atan2(LibcDetail::ToWide(y), LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI cbrtl_nid_postfix(GuestLongDouble x) { constexpr std::uint32_t B1 = 709958130; constexpr int Bias = 0x3fff; ExtendedParts parts = SplitExtended(x); @@ -1073,12 +1083,12 @@ long double APS5_VABI cbrtl_nid_postfix(long double x) { if (k == 0x7fff) return x + x; if (k == 0) { if (parts.mantissa == 0) return x; - parts = SplitExtended(x * 0x1p514L); + parts = SplitExtended(x * APS5_GUEST_LONG_DOUBLE(0x1p514L, 0x4201, 0x8000000000000000ull)); k = (parts.signExponent & 0x7fff) - (Bias + 514); } else { k -= Bias; } - long double reduced = JoinExtended(parts.mantissa, Bias); + GuestLongDouble reduced = JoinExtended(parts.mantissa, Bias); switch (k % 3) { case 1: case -2: @@ -1091,7 +1101,7 @@ long double APS5_VABI cbrtl_nid_postfix(long double x) { k -= 2; break; } - const long double scale = JoinExtended(0x8000000000000000ull, static_cast(sign | (Bias + k / 3))); + const GuestLongDouble scale = JoinExtended(0x8000000000000000ull, static_cast(sign | (Bias + k / 3))); const float estimate = std::bit_cast((std::bit_cast(static_cast(reduced)) & 0x7fffffffu) / 3 + B1); const double dx = static_cast(reduced); double dt = estimate; @@ -1101,27 +1111,27 @@ long double APS5_VABI cbrtl_nid_postfix(long double x) { dt = dt * (dx + dx + dr) / (dx + dr + dr); const volatile double shift = 0x1p32; const volatile double away = 0x1p-31; - long double t = (static_cast(shift) + away + dt) - 0x1p32L; - long double r = reduced / (t * t); + GuestLongDouble t = (static_cast(shift) + away + dt) - APS5_GUEST_LONG_DOUBLE(0x1p32L, 0x401f, 0x8000000000000000ull); + GuestLongDouble r = reduced / (t * t); r = (r - t) / ((t + t) + r); t = t + t * r; return t * scale; } -long double APS5_VABI erfl_nid_postfix(long double x) { return std::erf(x); } -long double APS5_VABI erfcl_nid_postfix(long double x) { return std::erfc(x); } -long double APS5_VABI expm1l_nid_postfix(long double x) { return std::expm1(x); } -long double APS5_VABI log1pl_nid_postfix(long double x) { return std::log1p(x); } -long double APS5_VABI log2l_nid_postfix(long double x) { return std::log2(x); } -long double APS5_VABI lgammal_nid_postfix(long double x) { return std::lgamma(x); } -long double APS5_VABI tgammal_nid_postfix(long double x) { return std::tgamma(x); } +GuestLongDouble APS5_VABI erfl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::erf(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI erfcl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::erfc(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI expm1l_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::expm1(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI log1pl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::log1p(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI log2l_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::log2(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI lgammal_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::lgamma(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI tgammal_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::tgamma(LibcDetail::ToWide(x))); } float APS5_VABI tgammaf_nid_postfix(float x) { return GuestTgammaf(x); } -long double APS5_VABI hypotl_nid_postfix(long double x, long double y) { - if (std::isinf(x) || std::isinf(y)) return std::numeric_limits::infinity(); - return std::hypot(x, y); +GuestLongDouble APS5_VABI hypotl_nid_postfix(GuestLongDouble x, GuestLongDouble y) { + if (LibcDetail::IsInf(x) || LibcDetail::IsInf(y)) return std::numeric_limits::infinity(); + return LibcDetail::FromWide(std::hypot(LibcDetail::ToWide(x), LibcDetail::ToWide(y))); } float APS5_VABI hypot3f_nid_postfix(float x, float y, float z) { return Hypot3(x, y, z, hypotf_nid_postfix); } double APS5_VABI hypot3_nid_postfix(double x, double y, double z) { return Hypot3(x, y, z, hypot_nid_postfix); } -long double APS5_VABI hypot3l_nid_postfix(long double x, long double y, long double z) { return Hypot3(x, y, z, hypotl_nid_postfix); } +GuestLongDouble APS5_VABI hypot3l_nid_postfix(GuestLongDouble x, GuestLongDouble y, GuestLongDouble z) { return Hypot3(x, y, z, hypotl_nid_postfix); } float APS5_VABI j0f_nid_postfix(float x) { return static_cast(::j0(x)); } float APS5_VABI j1f_nid_postfix(float x) { return static_cast(::j1(x)); } float APS5_VABI jnf_nid_postfix(int order, float x) { return static_cast(::jn(order, x)); } @@ -1129,7 +1139,7 @@ float APS5_VABI y0f_nid_postfix(float x) { return x == 0.0f ? GuestBesselSecondK float APS5_VABI y1f_nid_postfix(float x) { return x == 0.0f ? GuestBesselSecondKindZero() : static_cast(::y1(x)); } float APS5_VABI ynf_nid_postfix(int order, float x) { return x == 0.0f ? GuestBesselSecondKindZero() : static_cast(::yn(order, x)); } -long double APS5_VABI asinl_nid_postfix(long double x) { +GuestLongDouble APS5_VABI asinl_nid_postfix(GuestLongDouble x) { const ExtendedParts parts = SplitExtended(x); const int exponent = parts.signExponent & 0x7fff; if (exponent >= 0x3fff) { @@ -1139,29 +1149,29 @@ long double APS5_VABI asinl_nid_postfix(long double x) { return (x - x) / (x - x); } if (exponent < 0x3ffe) { - if (exponent < 0x3fff - 32 && 1.0e300L + x > 1.0L) return x; - const long double t = x * x; + if (exponent < 0x3fff - 32 && APS5_GUEST_LONG_DOUBLE(1.0e300L, 0x43e3, 0xbf21e44003acdd2d) + x > 1) return x; + const GuestLongDouble t = x * x; return x + x * (AsinNumerator(t) / AsinDenominator(t)); } - const long double t = (1.0L - std::fabs(x)) * 0.5L; - const long double p = AsinNumerator(t); - const long double q = AsinDenominator(t); - const long double s = std::sqrt(t); - long double result; + const GuestLongDouble t = (1 - LibcDetail::Fabs(x)) * 0.5; + const GuestLongDouble p = AsinNumerator(t); + const GuestLongDouble q = AsinDenominator(t); + const GuestLongDouble s = LibcDetail::Sqrt(t); + GuestLongDouble result; if ((parts.mantissa >> 32) >= 0xf3333333u) { - result = HalfPiHigh - (2.0L * (s + s * (p / q)) - HalfPiLow); + result = HalfPiHigh - (2 * (s + s * (p / q)) - HalfPiLow); } else { - const long double w = ClearLowMantissaWord(s); - const long double c = (t - w * w) / (s + w); - const long double r = p / q; - result = QuarterPiHigh - ((2.0L * s * r - (HalfPiLow - 2.0L * c)) - (QuarterPiHigh - 2.0L * w)); + const GuestLongDouble w = ClearLowMantissaWord(s); + const GuestLongDouble c = (t - w * w) / (s + w); + const GuestLongDouble r = p / q; + result = QuarterPiHigh - ((2 * s * r - (HalfPiLow - 2 * c)) - (QuarterPiHigh - 2 * w)); } return (parts.signExponent & 0x8000) != 0 ? -result : result; } -long double APS5_VABI frexpl_nid_postfix(long double x, int* exponent) { - if (!std::isfinite(x)) return x; - return std::frexp(x, exponent); +GuestLongDouble APS5_VABI frexpl_nid_postfix(GuestLongDouble x, int* exponent) { + if (!LibcDetail::IsFinite(x)) return x; + return LibcDetail::Frexp(x, exponent); } /* * ==================================================== @@ -3506,11 +3516,21 @@ float APS5_VABI cbrtf_nid_postfix(float x) { T = T * (static_cast(x) + x + r) / (x + r + r); return static_cast(T); } +#ifdef ANYPS5_GUEST_LIFTED +static GuestLongDouble PartialRemainder(GuestLongDouble x, GuestLongDouble y, std::uint16_t* status) { + long long quotient = 0; + const GuestLongDouble remainder = LibcDetail::Remainder(x, y, true, "ient); + const auto bits = static_cast(quotient < 0 ? -quotient : quotient); + *status = static_cast((bits >> 2 & 1) << 8 | (bits >> 1 & 1) << 14 | (bits & 1) << 9); + return remainder; +} +#else static long double PartialRemainder(long double x, long double y, std::uint16_t* status) { long double remainder; asm("1:\n\tfprem1\n\tfnstsw %%ax\n\ttestw $0x400, %%ax\n\tjnz 1b" : "=t"(remainder), "=a"(*status) : "0"(x), "u"(y)); return remainder; } +#endif static void SetRemainderDomainError(long double x, long double y) { if (!std::isnan(x) && !std::isnan(y) && (y == 0 || std::isinf(x))) { @@ -3525,7 +3545,7 @@ static int RemquoQuotient(std::uint16_t status, bool negative) { float APS5_VABI remainderf_nid_postfix(float x, float y) { std::uint16_t status; - const long double remainder = PartialRemainder(x, y, &status); + const GuestLongDouble remainder = PartialRemainder(x, y, &status); SetRemainderDomainError(x, y); return static_cast(remainder); } @@ -3686,22 +3706,22 @@ float APS5_VABI tanhf_nid_postfix(float x) { } return jx >= 0 ? z : -z; } -long double APS5_VABI atanl_nid_postfix(long double x) { return std::atan(x); } -long double APS5_VABI sinl_nid_postfix(long double x) { return std::sin(x); } -long double APS5_VABI cosl_nid_postfix(long double x) { return std::cos(x); } -long double APS5_VABI tanl_nid_postfix(long double x) { return std::tan(x); } -long double APS5_VABI sinhl_nid_postfix(long double x) { return std::sinh(x); } -long double APS5_VABI coshl_nid_postfix(long double x) { return std::cosh(x); } -long double APS5_VABI tanhl_nid_postfix(long double x) { return std::tanh(x); } -long double APS5_VABI expl_nid_postfix(long double x) { return std::exp(x); } -long double APS5_VABI exp2l_nid_postfix(long double x) { return std::exp2(x); } -long double APS5_VABI logl_nid_postfix(long double x) { return std::log(x); } -long double APS5_VABI log10l_nid_postfix(long double x) { return std::log10(x); } -long double APS5_VABI powl_nid_postfix(long double base, long double exp) { return std::pow(base, exp); } -long double APS5_VABI ldexpl_nid_postfix(long double x, int exp) { return std::ldexp(x, exp); } -long double APS5_VABI truncl_nid_postfix(long double x) { return std::trunc(x); } -long double APS5_VABI ceill_nid_postfix(long double x) { return std::ceil(x); } -long double APS5_VABI floorl_nid_postfix(long double x) { return std::floor(x); } +GuestLongDouble APS5_VABI atanl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::atan(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI sinl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::sin(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI cosl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::cos(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI tanl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::tan(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI sinhl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::sinh(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI coshl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::cosh(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI tanhl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::tanh(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI expl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::exp(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI exp2l_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::exp2(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI logl_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::log(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI log10l_nid_postfix(GuestLongDouble x) { return LibcDetail::FromWide(std::log10(LibcDetail::ToWide(x))); } +GuestLongDouble APS5_VABI powl_nid_postfix(GuestLongDouble base, GuestLongDouble exp) { return LibcDetail::FromWide(std::pow(LibcDetail::ToWide(base), LibcDetail::ToWide(exp))); } +GuestLongDouble APS5_VABI ldexpl_nid_postfix(GuestLongDouble x, int exp) { return LibcDetail::Scalbn(x, exp); } +GuestLongDouble APS5_VABI truncl_nid_postfix(GuestLongDouble x) { return LibcDetail::RoundIntegral(x, LibcDetail::IntegralRounding::TowardZero); } +GuestLongDouble APS5_VABI ceill_nid_postfix(GuestLongDouble x) { return LibcDetail::RoundIntegral(x, LibcDetail::IntegralRounding::Upward); } +GuestLongDouble APS5_VABI floorl_nid_postfix(GuestLongDouble x) { return LibcDetail::RoundIntegral(x, LibcDetail::IntegralRounding::Downward); } double APS5_VABI logb_nid_postfix(double x) { const std::uint64_t bits = std::bit_cast(x) & 0x7fffffffffffffffull; if (bits == 0) { @@ -3728,20 +3748,20 @@ int APS5_VABI ilogb_nid_postfix(double x) { int APS5_VABI ilogbf_nid_postfix(float x) { return ilogb_nid_postfix(static_cast(x)); } double APS5_VABI remainder_nid_postfix(double x, double y) { std::uint16_t status; - const long double remainder = PartialRemainder(x, y, &status); + const GuestLongDouble remainder = PartialRemainder(x, y, &status); SetRemainderDomainError(x, y); return static_cast(remainder); } double APS5_VABI remquo_nid_postfix(double x, double y, int* quo) { std::uint16_t status; - const long double remainder = PartialRemainder(x, y, &status); + const GuestLongDouble remainder = PartialRemainder(x, y, &status); *quo = RemquoQuotient(status, std::signbit(x) != std::signbit(y)); SetRemainderDomainError(x, y); return static_cast(remainder); } float APS5_VABI remquof_nid_postfix(float x, float y, int* quo) { std::uint16_t status; - const long double remainder = PartialRemainder(x, y, &status); + const GuestLongDouble remainder = PartialRemainder(x, y, &status); *quo = RemquoQuotient(status, std::signbit(x) != std::signbit(y)); SetRemainderDomainError(x, y); return static_cast(remainder); @@ -3795,13 +3815,13 @@ int APS5_VABI isnan_nid_postfix(double x) { return __isnan_nid_postfix(x); } int APS5_VABI isinf_nid_postfix(double x) { return __isinf_nid_postfix(x); } int APS5_VABI __fpclassifyf_nid_postfix(float x) { return _FDtest_nid_postfix(&x); } int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 0x8000 : 0; } -int APS5_VABI __signbitl_nid_postfix(long double x) { - static_assert(sizeof(long double) == 16, "guest long double requires x87 extended precision storage"); - return std::signbit(x) ? 0x8000 : 0; +int APS5_VABI __signbitl_nid_postfix(GuestLongDouble x) { + static_assert(sizeof(GuestLongDouble) == 16, "guest GuestLongDouble requires x87 extended precision storage"); + return LibcDetail::SignBit(x) ? 0x8000 : 0; } -int APS5_VABI __isfinitel_nid_postfix(long double x) { - static_assert(sizeof(long double) == 16 && std::numeric_limits::max_exponent == 16384 && std::endian::native == std::endian::little, "guest long double requires little-endian storage with the x87 exponent range"); +int APS5_VABI __isfinitel_nid_postfix(GuestLongDouble x) { + static_assert(sizeof(GuestLongDouble) == 16 && std::numeric_limits::max_exponent == 16384 && std::endian::native == std::endian::little, "guest GuestLongDouble requires little-endian storage with the x87 exponent range"); std::uint16_t exponent; std::memcpy(&exponent, LibcDetail::GuestLongDoubleImage(x).data() + 8, sizeof(exponent)); return (exponent & 0x7fffu) != 0x7fffu; @@ -3885,50 +3905,50 @@ double APS5_VABI _Cosh_nid_postfix(double x, double y) { return std::scalbn(scale * (0.5 * std::exp(reduced)), scaleExponent + exponent); } -long double APS5_VABI _LCosh_nid_postfix(long double x, long double y) { - if (!std::isfinite(x) || !std::isfinite(y)) return y * std::cosh(x); - if (y == 0.0L) return std::copysign(0.0L, y); - constexpr long double Ln2High = 0x1.62e42fefa0000p-1L; - constexpr long double Ln2Low = 0x1.cf79abc9e3b398p-40L; - constexpr long double InverseLn2 = 0x1.71547652b82fe178p+0L; - const long double magnitude = std::abs(x); - if (magnitude <= (std::numeric_limits::max_exponent - 1) * (Ln2High + Ln2Low)) - return y * std::cosh(x); +GuestLongDouble APS5_VABI _LCosh_nid_postfix(GuestLongDouble x, GuestLongDouble y) { + if (!LibcDetail::IsFinite(x) || !LibcDetail::IsFinite(y)) return y * LibcDetail::FromWide(std::cosh(LibcDetail::ToWide(x))); + if (y == 0) return LibcDetail::CopySign(GuestLongDouble {}, y); + constexpr GuestLongDouble Ln2High = 0x1.62e42fefa0000p-1; + constexpr GuestLongDouble Ln2Low = APS5_GUEST_LONG_DOUBLE(0x1.cf79abc9e3b398p-40L, 0x3fd7, 0xe7bcd5e4f1d9cc00); + constexpr GuestLongDouble InverseLn2 = APS5_GUEST_LONG_DOUBLE(0x1.71547652b82fe178p+0L, 0x3fff, 0xb8aa3b295c17f0bc); + const GuestLongDouble magnitude = LibcDetail::Fabs(x); + if (magnitude <= (std::numeric_limits::max_exponent - 1) * (Ln2High + Ln2Low)) + return y * LibcDetail::FromWide(std::cosh(LibcDetail::ToWide(x))); int scaleExponent; - const long double scale = std::frexp(y, &scaleExponent); - constexpr int MaximumExponentSpan = std::numeric_limits::max_exponent - - std::numeric_limits::min_exponent + std::numeric_limits::digits + 4; + const GuestLongDouble scale = LibcDetail::Frexp(y, &scaleExponent); + constexpr int MaximumExponentSpan = std::numeric_limits::max_exponent - + std::numeric_limits::min_exponent + std::numeric_limits::digits + 4; if (magnitude > MaximumExponentSpan * (Ln2High + Ln2Low)) - return std::scalbn(scale, INT_MAX); + return LibcDetail::Scalbn(scale, INT_MAX); const int exponent = static_cast(magnitude * InverseLn2); - const long double negatedExponent = -exponent; - const long double reduced = std::fma(negatedExponent, Ln2Low, std::fma(negatedExponent, Ln2High, magnitude)); - return std::scalbn(scale * (0.5L * std::exp(reduced)), scaleExponent + exponent); -} - -long double APS5_VABI _LSinh_nid_postfix(long double x, long double y) { - if (!std::isfinite(x) || !std::isfinite(y)) return y * std::sinh(x); - if (y == 0.0L) { - const bool negative = std::signbit(x) != std::signbit(y); - return std::copysign(0.0L, negative ? -1.0L : 1.0L); - } - constexpr long double Ln2High = 0x1.62e42fefa0000p-1L; - constexpr long double Ln2Low = 0x1.cf79abc9e3b398p-40L; - constexpr long double InverseLn2 = 0x1.71547652b82fe178p+0L; - const long double magnitude = std::abs(x); - if (magnitude <= (std::numeric_limits::max_exponent - 1) * (Ln2High + Ln2Low)) - return y * std::sinh(x); + const GuestLongDouble negatedExponent = -exponent; + const GuestLongDouble reduced = LibcDetail::Fma(negatedExponent, Ln2Low, LibcDetail::Fma(negatedExponent, Ln2High, magnitude)); + return LibcDetail::Scalbn(scale * (0.5 * LibcDetail::FromWide(std::exp(LibcDetail::ToWide(reduced)))), scaleExponent + exponent); +} + +GuestLongDouble APS5_VABI _LSinh_nid_postfix(GuestLongDouble x, GuestLongDouble y) { + if (!LibcDetail::IsFinite(x) || !LibcDetail::IsFinite(y)) return y * LibcDetail::FromWide(std::sinh(LibcDetail::ToWide(x))); + if (y == 0) { + const bool negative = LibcDetail::SignBit(x) != LibcDetail::SignBit(y); + return LibcDetail::CopySign(GuestLongDouble {}, GuestLongDouble(negative ? -1 : 1)); + } + constexpr GuestLongDouble Ln2High = 0x1.62e42fefa0000p-1; + constexpr GuestLongDouble Ln2Low = APS5_GUEST_LONG_DOUBLE(0x1.cf79abc9e3b398p-40L, 0x3fd7, 0xe7bcd5e4f1d9cc00); + constexpr GuestLongDouble InverseLn2 = APS5_GUEST_LONG_DOUBLE(0x1.71547652b82fe178p+0L, 0x3fff, 0xb8aa3b295c17f0bc); + const GuestLongDouble magnitude = LibcDetail::Fabs(x); + if (magnitude <= (std::numeric_limits::max_exponent - 1) * (Ln2High + Ln2Low)) + return y * LibcDetail::FromWide(std::sinh(LibcDetail::ToWide(x))); int scaleExponent; - const long double scale = std::frexp(y, &scaleExponent); - constexpr int MaximumExponentSpan = std::numeric_limits::max_exponent - - std::numeric_limits::min_exponent + std::numeric_limits::digits + 4; - const long double sign = std::copysign(1.0L, x); + const GuestLongDouble scale = LibcDetail::Frexp(y, &scaleExponent); + constexpr int MaximumExponentSpan = std::numeric_limits::max_exponent - + std::numeric_limits::min_exponent + std::numeric_limits::digits + 4; + const GuestLongDouble sign = LibcDetail::CopySign(GuestLongDouble(1), x); if (magnitude > MaximumExponentSpan * (Ln2High + Ln2Low)) - return sign * std::scalbn(scale, INT_MAX); + return sign * LibcDetail::Scalbn(scale, INT_MAX); const int exponent = static_cast(magnitude * InverseLn2); - const long double negatedExponent = -exponent; - const long double reduced = std::fma(negatedExponent, Ln2Low, std::fma(negatedExponent, Ln2High, magnitude)); - return sign * std::scalbn(scale * (0.5L * std::exp(reduced)), scaleExponent + exponent); + const GuestLongDouble negatedExponent = -exponent; + const GuestLongDouble reduced = LibcDetail::Fma(negatedExponent, Ln2Low, LibcDetail::Fma(negatedExponent, Ln2High, magnitude)); + return sign * LibcDetail::Scalbn(scale * (0.5 * LibcDetail::FromWide(std::exp(LibcDetail::ToWide(reduced)))), scaleExponent + exponent); } int APS5_VABI _Fltrounds_nid_postfix(void) { @@ -3980,7 +4000,7 @@ union SonyDconst { unsigned short _Word[8]; float _Float; double _Double; - long double _Long_double; + GuestLongDouble _Long_double; }; static_assert(std::endian::native == std::endian::little); diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp index 6c97a009aba0..d99488afac40 100644 --- a/core/libs/prx/libc/src/MathExact.cpp +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -40,7 +40,7 @@ ExtendedBits Bits(const void* bytes) { return bits; } -ExtendedBits Bits(const long double& value) { +ExtendedBits Bits(const LibcDetail::GuestLongDouble& value) { return Bits(LibcDetail::GuestLongDoubleImage(value).data()); } @@ -72,11 +72,12 @@ short TestFloat(TFloat value) { template T Remainder(T x, T y) { - const bool domain = !std::isnan(x) && !std::isnan(y) && (std::isinf(x) || y == T(0)); + const bool domain = !LibcDetail::IsNan(x) && !LibcDetail::IsNan(y) && (LibcDetail::IsInf(x) || y == T(0)); T result; { ErrnoGuard guard; - result = std::remainder(x, y); + long long quotient = 0; + result = LibcDetail::Remainder(x, y, true, "ient); } if (domain) errno = EDOM; return result; @@ -86,8 +87,8 @@ int Exponent(std::int64_t exponent) { return static_cast(std::clamp(exponent, INT_MIN, INT_MAX)); } -void StoreExtended(long double* value, const ExtendedBits& bits) { - unsigned char bytes[sizeof(long double)] = {}; +void StoreExtended(LibcDetail::GuestLongDouble* value, const ExtendedBits& bits) { + unsigned char bytes[sizeof(LibcDetail::GuestLongDouble)] = {}; std::memcpy(bytes, value, sizeof(bytes)); std::memcpy(bytes, &bits.mantissa, sizeof(bits.mantissa)); std::memcpy(bytes + 8, &bits.signExponent, sizeof(bits.signExponent)); @@ -98,11 +99,13 @@ bool ExtendedNan(const ExtendedBits& bits) { return (bits.signExponent & 0x7fff) == 0x7fff && (bits.mantissa & 0x7fffffffffffffffull) != 0; } +#ifndef ANYPS5_GUEST_LIFTED long double ExtendedValue(const ExtendedBits& bits) { const int exponent = bits.signExponent & 0x7fff; const long double magnitude = std::ldexp(static_cast(bits.mantissa), (exponent == 0 ? 1 : exponent) - 16383 - 63); return (bits.signExponent & 0x8000) != 0 ? -magnitude : magnitude; } +#endif template short DropFraction(void* value, short scale) { @@ -276,101 +279,104 @@ std::int64_t ConvertToInteger(double x) { extern "C" { -long double APS5_VABI copysignl_nid_postfix(long double x, long double y) { return std::copysign(x, y); } -long double APS5_VABI fdiml_nid_postfix(long double x, long double y) { - if (std::isnan(x)) return x; - if (std::isnan(y)) return y; - return x > y ? x - y : 0.0L; +LibcDetail::GuestLongDouble APS5_VABI copysignl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y) { return LibcDetail::CopySign(x, y); } +LibcDetail::GuestLongDouble APS5_VABI fdiml_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y) { + if (LibcDetail::IsNan(x)) return x; + if (LibcDetail::IsNan(y)) return y; + return x > y ? x - y : LibcDetail::GuestLongDouble {}; } -long double APS5_VABI fmodl_nid_postfix(long double x, long double y) { - if (!std::isfinite(x) || y == 0.0L || std::isnan(y)) { +LibcDetail::GuestLongDouble APS5_VABI fmodl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y) { + if (!LibcDetail::IsFinite(x) || y == 0 || LibcDetail::IsNan(y)) { errno = EDOM; return (x * y) / (x * y); } ErrnoGuard guard; - return std::fmod(x, y); + long long quotient = 0; + return LibcDetail::Remainder(x, y, false, "ient); } -long double APS5_VABI remainderl_nid_postfix(long double x, long double y) { +LibcDetail::GuestLongDouble APS5_VABI remainderl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y) { return Remainder(x, y); } -long double APS5_VABI remquol_nid_postfix(long double x, long double y, int* quotient) { - const bool domain = !std::isnan(x) && !std::isnan(y) && (std::isinf(x) || y == 0.0L); - int bits = 0; - long double result; +LibcDetail::GuestLongDouble APS5_VABI remquol_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y, int* quotient) { + const bool domain = !LibcDetail::IsNan(x) && !LibcDetail::IsNan(y) && (LibcDetail::IsInf(x) || y == 0); + long long bits = 0; + LibcDetail::GuestLongDouble result; { ErrnoGuard guard; - result = std::remquo(x, y, &bits); + result = LibcDetail::Remainder(x, y, true, &bits); } if (domain) errno = EDOM; - if (!std::isnan(result)) *quotient = bits < 0 ? -(-bits & 7) : (bits & 7); + if (!LibcDetail::IsNan(result)) *quotient = static_cast(bits < 0 ? -(-bits & 7) : (bits & 7)); return result; } -long double APS5_VABI logbl_nid_postfix(long double x) { +LibcDetail::GuestLongDouble APS5_VABI logbl_nid_postfix(LibcDetail::GuestLongDouble x) { ErrnoGuard guard; - return std::logb(x); + return LibcDetail::Logb(x); } -int APS5_VABI ilogbl_nid_postfix(long double x) { +int APS5_VABI ilogbl_nid_postfix(LibcDetail::GuestLongDouble x) { if ((Bits(x).signExponent & 0x7fff) == 0x7fff) return INT_MAX; ErrnoGuard guard; - return std::ilogb(x); + return LibcDetail::Ilogb(x); } -long double APS5_VABI rintl_nid_postfix(long double x) { return std::rint(x); } -long double APS5_VABI roundl_nid_postfix(long double x) { return std::isfinite(x) ? std::round(x) : x; } +LibcDetail::GuestLongDouble APS5_VABI rintl_nid_postfix(LibcDetail::GuestLongDouble x) { return LibcDetail::Rint(x); } +LibcDetail::GuestLongDouble APS5_VABI roundl_nid_postfix(LibcDetail::GuestLongDouble x) { + return LibcDetail::IsFinite(x) ? LibcDetail::RoundIntegral(x, LibcDetail::IntegralRounding::HalfAwayFromZero) : x; +} -std::int64_t APS5_VABI lroundl_nid_postfix(long double x) { +std::int64_t APS5_VABI lroundl_nid_postfix(LibcDetail::GuestLongDouble x) { ErrnoGuard guard; - return std::llround(x); + return LibcDetail::LlRound(x); } -std::int64_t APS5_VABI lrintl_nid_postfix(long double x) { +std::int64_t APS5_VABI lrintl_nid_postfix(LibcDetail::GuestLongDouble x) { ErrnoGuard guard; - return std::llrint(x); + return LibcDetail::LlRint(x); } -long long APS5_VABI llrintl_nid_postfix(long double x) { +long long APS5_VABI llrintl_nid_postfix(LibcDetail::GuestLongDouble x) { ErrnoGuard guard; - return std::llrint(x); + return LibcDetail::LlRint(x); } -long double APS5_VABI sqrtl_nid_postfix(long double x) { - return std::sqrt(x); +LibcDetail::GuestLongDouble APS5_VABI sqrtl_nid_postfix(LibcDetail::GuestLongDouble x) { + return LibcDetail::Sqrt(x); } -long double APS5_VABI modfl_nid_postfix(long double x, long double* integral) { - if (std::isnan(x)) { +LibcDetail::GuestLongDouble APS5_VABI modfl_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble* integral) { + if (LibcDetail::IsNan(x)) { LibcDetail::GuestLongDoubleStore(integral, x); return x; } - long double whole; - const long double fraction = std::modf(x, &whole); + LibcDetail::GuestLongDouble whole; + const LibcDetail::GuestLongDouble fraction = LibcDetail::Modf(x, &whole); LibcDetail::GuestLongDoubleStore(integral, whole); return fraction; } -long double APS5_VABI nanl_nid_postfix(const char*) { return std::numeric_limits::quiet_NaN(); } +LibcDetail::GuestLongDouble APS5_VABI nanl_nid_postfix(const char*) { return std::numeric_limits::quiet_NaN(); } -long double APS5_VABI scalblnl_nid_postfix(long double x, std::int64_t exponent) { +LibcDetail::GuestLongDouble APS5_VABI scalblnl_nid_postfix(LibcDetail::GuestLongDouble x, std::int64_t exponent) { ErrnoGuard guard; - return std::scalbn(x, static_cast(exponent)); + return LibcDetail::Scalbn(x, static_cast(exponent)); } -long double APS5_VABI fmal_nid_postfix(long double x, long double y, long double z) { - if (x == 0.0L || y == 0.0L) return x * y + z; - if (z == 0.0L) return x * y; - if (!std::isfinite(x) || !std::isfinite(y)) return x * y + z; - if (!std::isfinite(z)) return z; +LibcDetail::GuestLongDouble APS5_VABI fmal_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y, LibcDetail::GuestLongDouble z) { + if (x == 0 || y == 0) return x * y + z; + if (z == 0) return x * y; + if (!LibcDetail::IsFinite(x) || !LibcDetail::IsFinite(y)) return x * y + z; + if (!LibcDetail::IsFinite(z)) return z; int xExponent = 0, yExponent = 0, zExponent = 0; - std::frexp(x, &xExponent); - std::frexp(y, &yExponent); - std::frexp(z, &zExponent); - if (xExponent + yExponent - zExponent > 2 * std::numeric_limits::digits) return x * y; + LibcDetail::Frexp(x, &xExponent); + LibcDetail::Frexp(y, &yExponent); + LibcDetail::Frexp(z, &zExponent); + if (xExponent + yExponent - zExponent > 2 * std::numeric_limits::digits) return x * y; ErrnoGuard guard; - return std::fma(x, y, z); + return LibcDetail::Fma(x, y, z); } double APS5_VABI floor_nid_postfix(double x) { return std::isnan(x) ? x + x : std::floor(x); } @@ -439,30 +445,30 @@ float APS5_VABI scalbf_nid_postfix(float x, float exponent) { int APS5_VABI finite_nid_postfix(double x) { return std::isfinite(x) ? 1 : 0; } int APS5_VABI finitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0; } -int APS5_VABI __fpclassifyl_nid_postfix(long double x) { +int APS5_VABI __fpclassifyl_nid_postfix(LibcDetail::GuestLongDouble x) { const ExtendedBits bits = Bits(x); const int exponent = bits.signExponent & 0x7fff; if (exponent == 0) return bits.mantissa != 0 ? Denormal : Zero; return exponent != 0x7fff ? Finite : TestExtended(bits); } -int APS5_VABI __isinfl_nid_postfix(long double x) { return TestExtended(Bits(x)) == Infinite ? 1 : 0; } -int APS5_VABI __isnanl_nid_postfix(long double x) { return TestExtended(Bits(x)) == NotANumber ? 1 : 0; } +int APS5_VABI __isinfl_nid_postfix(LibcDetail::GuestLongDouble x) { return TestExtended(Bits(x)) == Infinite ? 1 : 0; } +int APS5_VABI __isnanl_nid_postfix(LibcDetail::GuestLongDouble x) { return TestExtended(Bits(x)) == NotANumber ? 1 : 0; } -int APS5_VABI __isnormall_nid_postfix(long double x) { +int APS5_VABI __isnormall_nid_postfix(LibcDetail::GuestLongDouble x) { const int exponent = Bits(x).signExponent & 0x7fff; return exponent != 0 && exponent != 0x7fff ? 1 : 0; } -short APS5_VABI _LDtest_nid_postfix(const long double* value) { return TestExtended(Bits(static_cast(value))); } +short APS5_VABI _LDtest_nid_postfix(const LibcDetail::GuestLongDouble* value) { return TestExtended(Bits(static_cast(value))); } int APS5_VABI _Dclass_nid_postfix(double x) { return TestFloat(x); } int APS5_VABI _FDclass_nid_postfix(float x) { return TestFloat(x); } -int APS5_VABI _LDclass_nid_postfix(long double x) { return TestExtended(Bits(x)); } -short APS5_VABI _LDsign_nid_postfix(long double x) { return static_cast(Bits(x).signExponent & 0x8000); } +int APS5_VABI _LDclass_nid_postfix(LibcDetail::GuestLongDouble x) { return TestExtended(Bits(x)); } +short APS5_VABI _LDsign_nid_postfix(LibcDetail::GuestLongDouble x) { return static_cast(Bits(x).signExponent & 0x8000); } short APS5_VABI _Dint_nid_postfix(double* value, short scale) { return DropFraction(value, scale); } short APS5_VABI _FDint_nid_postfix(float* value, short scale) { return DropFraction(value, scale); } -short APS5_VABI _LDint_nid_postfix(long double* value, short scale) { +short APS5_VABI _LDint_nid_postfix(LibcDetail::GuestLongDouble* value, short scale) { ExtendedBits bits = Bits(*value); const int field = bits.signExponent & 0x7fff; if (field == 0x7fff) return ExtendedNan(bits) ? NotANumber : Infinite; @@ -483,23 +489,31 @@ short APS5_VABI _LDint_nid_postfix(long double* value, short scale) { short APS5_VABI _Dscale_nid_postfix(double* value, std::int64_t exponent) { return ScaleBinary(value, exponent); } short APS5_VABI _FDscale_nid_postfix(float* value, std::int64_t exponent) { return ScaleBinary(value, exponent); } -short APS5_VABI _LDscale_nid_postfix(long double* value, std::int64_t exponent) { +short APS5_VABI _LDscale_nid_postfix(LibcDetail::GuestLongDouble* value, std::int64_t exponent) { const ExtendedBits bits = Bits(*value); if ((bits.signExponent & 0x7fff) == 0x7fff) return ExtendedNan(bits) ? NotANumber : Infinite; if (bits.mantissa == 0) { StoreExtended(value, {0, static_cast(bits.signExponent & 0x8000)}); return Zero; } +#ifdef ANYPS5_GUEST_LIFTED + *value = LibcDetail::Scalbn(*value, static_cast(std::clamp(exponent, -100000, 100000))); + if (LibcDetail::IsInf(*value)) return Infinite; + if (*value != 0) return Finite; + *value = LibcDetail::GuestLongDouble{}; + return Zero; +#else *value = ExtendedValue(bits); const short code = ScaleBinary(value, exponent); if (code == Zero) *value = 0.0L; return code; +#endif } short APS5_VABI _Dunscale_nid_postfix(short* exponent, double* value) { return UnscaleBinary(exponent, value); } short APS5_VABI _FDunscale_nid_postfix(short* exponent, float* value) { return UnscaleBinary(exponent, value); } -short APS5_VABI _LDunscale_nid_postfix(short* exponent, long double* value) { +short APS5_VABI _LDunscale_nid_postfix(short* exponent, LibcDetail::GuestLongDouble* value) { const ExtendedBits bits = Bits(*value); if ((bits.signExponent & 0x7fff) == 0x7fff) { *exponent = 0; @@ -509,8 +523,15 @@ short APS5_VABI _LDunscale_nid_postfix(short* exponent, long double* value) { *exponent = 0; return Zero; } +#ifdef ANYPS5_GUEST_LIFTED + int unscaled = 0; + *value = LibcDetail::Frexp(*value, &unscaled); + *exponent = static_cast(unscaled); + return Finite; +#else *value = ExtendedValue(bits); return UnscaleBinary(exponent, value); +#endif } short APS5_VABI _ZNSt8__sce_v27_DscaleEPdl_nid_postfix(double* value, std::int64_t exponent) { return ScaleFieldBits(value, exponent); } @@ -544,13 +565,13 @@ short APS5_VABI _ZNSt8__sce_v27_FDnormEPNS_5_FvalE_nid_postfix(std::uint16_t* wo int APS5_VABI _Fpcomp_nid_postfix(double x, double y) { return std::isnan(x) || std::isnan(y) ? 0 : Compare(x, y); } int APS5_VABI _FFpcomp_nid_postfix(float x, float y) { return std::isnan(x) || std::isnan(y) ? 0 : Compare(x, y); } -int APS5_VABI _LFpcomp_nid_postfix(long double x, long double y) { return ExtendedNan(Bits(x)) || ExtendedNan(Bits(y)) ? 0 : Compare(x, y); } +int APS5_VABI _LFpcomp_nid_postfix(LibcDetail::GuestLongDouble x, LibcDetail::GuestLongDouble y) { return ExtendedNan(Bits(x)) || ExtendedNan(Bits(y)) ? 0 : Compare(x, y); } double APS5_VABI _Poly_nid_postfix(double x, const double* table, int degree) { return FusedPolynomial(x, table, degree); } float APS5_VABI _FPoly_nid_postfix(float x, const float* table, int degree) { return FusedPolynomial(x, table, degree); } -long double APS5_VABI _LPoly_nid_postfix(long double x, const long double* table, int degree) { - long double result = table[0]; +LibcDetail::GuestLongDouble APS5_VABI _LPoly_nid_postfix(LibcDetail::GuestLongDouble x, const LibcDetail::GuestLongDouble* table, int degree) { + LibcDetail::GuestLongDouble result = table[0]; for (int index = 1; index <= degree; ++index) { result *= x; result += table[index]; diff --git a/core/libs/prx/libc/src/ScanningWide.cpp b/core/libs/prx/libc/src/ScanningWide.cpp index bb0183972085..b19677674b67 100644 --- a/core/libs/prx/libc/src/ScanningWide.cpp +++ b/core/libs/prx/libc/src/ScanningWide.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/Api.hpp" #include "prx/libc/include/GuestLongDouble.hpp" +#include "prx/libc/include/ScanFloatField.hpp" #include #include #include @@ -19,7 +20,14 @@ namespace { -enum class Fit { Invalid, Prefix, Valid }; +using LibcDetail::DigitValue; +using LibcDetail::Fit; +using LibcDetail::FloatFit; +using LibcDetail::IsDecimal; +using LibcDetail::IsHex; +using LibcDetail::Lower; +using LibcDetail::StartsWord; +using LibcDetail::Unsigned; enum class Outcome { Done, Matching, Input, Violation }; @@ -37,38 +45,6 @@ bool IsSpace(char16_t character) { return iswspace_nid_postfix(character) != 0; } -int DigitValue(char character) { - if (character >= '0' && character <= '9') return character - '0'; - if (character >= 'a' && character <= 'z') return character - 'a' + 10; - if (character >= 'A' && character <= 'Z') return character - 'A' + 10; - return 36; -} - -bool IsDecimal(char character) { - return character >= '0' && character <= '9'; -} - -bool IsHex(char character) { - return DigitValue(character) < 16; -} - -char Lower(char character) { - return character >= 'A' && character <= 'Z' ? static_cast(character - 'A' + 'a') : character; -} - -bool StartsWord(std::string_view text, std::string_view word) { - if (text.size() > word.size()) return false; - for (std::size_t index = 0; index < text.size(); ++index) { - if (Lower(text[index]) != word[index]) return false; - } - return true; -} - -std::string_view Unsigned(std::string_view text) { - if (!text.empty() && (text[0] == '+' || text[0] == '-')) text.remove_prefix(1); - return text; -} - Fit IntegerFit(std::string_view text, int base) { text = Unsigned(text); if (text.empty()) return Fit::Prefix; @@ -87,43 +63,6 @@ Fit IntegerFit(std::string_view text, int base) { return Fit::Valid; } -Fit SpecialFloatFit(std::string_view text) { - if (StartsWord(text, "infinity")) return text.size() == 3 || text.size() == 8 ? Fit::Valid : Fit::Prefix; - if (StartsWord(text, "nan")) return text.size() == 3 ? Fit::Valid : Fit::Prefix; - if (text.size() < 4 || !StartsWord(text.substr(0, 3), "nan") || text[3] != '(') return Fit::Invalid; - for (std::size_t index = 4; index < text.size(); ++index) { - const char character = text[index]; - if (character == ')') return index + 1 == text.size() ? Fit::Valid : Fit::Invalid; - if (DigitValue(character) == 36 && character != '_') return Fit::Invalid; - } - return Fit::Prefix; -} - -Fit FloatFit(std::string_view text) { - text = Unsigned(text); - if (text.empty()) return Fit::Prefix; - if (Lower(text[0]) == 'i' || Lower(text[0]) == 'n') return SpecialFloatFit(text); - const bool hex = text.size() >= 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X'); - const auto digit = [hex](char character) { return hex ? IsHex(character) : IsDecimal(character); }; - std::size_t index = hex ? 2 : 0; - std::size_t digits = 0; - for (; index < text.size() && digit(text[index]); ++index) ++digits; - if (index < text.size() && text[index] == '.') { - for (++index; index < text.size() && digit(text[index]); ++index) ++digits; - } - if (index == text.size()) return digits != 0 ? Fit::Valid : Fit::Prefix; - if (digits == 0) return Fit::Invalid; - if (Lower(text[index]) != (hex ? 'p' : 'e')) return Fit::Invalid; - ++index; - if (index == text.size()) return Fit::Valid; - if (text[index] == '+' || text[index] == '-') ++index; - if (index == text.size()) return Fit::Valid; - for (; index < text.size(); ++index) { - if (!IsDecimal(text[index])) return Fit::Invalid; - } - return Fit::Valid; -} - class WideInput { const char16_t* const text; FileStream* const stream; diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index a66ddf14147d..1dc96eeae8c7 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -389,7 +389,7 @@ TFloat ParseFloat(const char* text, char** end, TFloat (*parse)(const char*, cha if (subject[0] != '-') return TFloat{0}; if constexpr (std::is_same_v) return subject[1] == '\0' ? TFloat{0} : -TFloat{0}; const bool special = std::strchr("iInN", subject[1]) != nullptr && subject[1] != '\0'; - return std::is_same_v && !special ? -TFloat{0} : TFloat{0}; + return std::is_same_v && !special ? -TFloat{0} : TFloat{0}; } if (*subject == '+' || *subject == '-') ++subject; const bool hexadecimal = subject[0] == '0' && (subject[1] == 'x' || subject[1] == 'X') && parsedEnd > subject + 2; @@ -656,9 +656,9 @@ double APS5_VABI atof_nid_postfix(const char* str) { return strtod_nid_postfix(s float APS5_VABI strtof_nid_postfix(const char* str, char** endptr) { return ParseFloat(str, endptr, std::strtof); } -long double APS5_VABI strtold_nid_postfix(const char* str, char** endptr) { - static_assert(sizeof(long double) == 16, "Guest long double requires x87 extended precision storage"); - return ParseFloat(str, endptr, LibcDetail::GuestLongDoubleParse); +LibcDetail::GuestLongDouble APS5_VABI strtold_nid_postfix(const char* str, char** endptr) { + static_assert(sizeof(LibcDetail::GuestLongDouble) == 16, "Guest long double requires x87 extended precision storage"); + return ParseFloat(str, endptr, LibcDetail::GuestLongDoubleParse); } int APS5_VABI atoi_nid_postfix(const char* str) { @@ -947,11 +947,11 @@ float APS5_VABI wcstof_nid_postfix(const char16_t* str, char16_t** endptr) { return ParseAsciiPrefix(str, endptr, [](const char* text, char** end) { return ParseFloat(text, end, std::strtof); }); } -long double APS5_VABI wcstold_nid_postfix(const char16_t* str, char16_t** endptr) { +LibcDetail::GuestLongDouble APS5_VABI wcstold_nid_postfix(const char16_t* str, char16_t** endptr) { #ifndef __EMSCRIPTEN__ - static_assert(sizeof(long double) == 16); + static_assert(sizeof(LibcDetail::GuestLongDouble) == 16); #endif - return ParseAsciiPrefix(str, endptr, [](const char* text, char** end) { return ParseFloat(text, end, LibcDetail::GuestLongDoubleParse); }); + return ParseAsciiPrefix(str, endptr, [](const char* text, char** end) { return ParseFloat(text, end, LibcDetail::GuestLongDoubleParse); }); } unsigned long long APS5_VABI wcstoul_nid_postfix(const char16_t* str, char16_t** endptr, int base) { @@ -1070,7 +1070,7 @@ float APS5_VABI _WStof_nid_postfix(const char16_t* str, char16_t** endptr, std:: return ParseAsciiPrefix(str, endptr, [pten](const char* text, char** end) { return _Stofx_nid_postfix(text, end, pten, nullptr); }); } -long double APS5_VABI _WStold_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten) { +LibcDetail::GuestLongDouble APS5_VABI _WStold_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten) { return ParseAsciiPrefix(str, endptr, [pten](const char* text, char** end) { return _Stoldx_nid_postfix(text, end, pten, nullptr); }); } diff --git a/core/libs/tests/GuestLongDouble.cpp b/core/libs/tests/GuestLongDouble.cpp index 5f0b78cb1306..1d9ca2e6f2f2 100644 --- a/core/libs/tests/GuestLongDouble.cpp +++ b/core/libs/tests/GuestLongDouble.cpp @@ -38,4 +38,7 @@ int main() { LibcDetail::GuestLongDoubleStore(stored.data(), loaded); if (std::memcmp(stored.data(), guest.data(), 10) != 0) std::abort(); } + for (const char* text : {"0.1", "2.718281828459045235360287471352662497757"}) { + if (LibcDetail::GuestLongDoubleParse(text, nullptr) != std::strtold(text, nullptr)) std::abort(); + } } diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index e3843354c725..72fd78d019a7 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -71,6 +71,9 @@ - [recallocarray](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) - adapted from OpenBSD `lib/libc/stdlib/recallocarray.c` at commit `2cb7cdfc21` (ISC licensed, Otto Moerbeek): console behavior is unmeasured. Delegates to `calloc` on null pointer, checks multiplication overflow on new and old counts setting `ENOMEM` (12) and `EINVAL` (22), zeroes small shrunken tails in place (under half the old size and modeled 16 KiB page size), taking old count on trust as in reference (a caller passing `oldnmemb` larger than the block zeroes past it), and otherwise allocates new storage, copies elements, zeroes new memory or clears the old allocation, and frees old pointer - [sceLibcMspaceReallocalign](../../../core/libs/prx/libc/src/Mspace.cpp) (libSceLibcInternal, implemented in libc) takes `(void* heap, void* block, size_t alignment, size_t size)`, the order of the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x13880, whose internal routine at 0x12390 treats the third argument as the alignment and, for a null block, passes it to the routine `sceLibcMspaceMemalign` uses). Measured on a PS5 (firmware 13.60) from a homebrew payload: a null block with alignment 4096 and size 100 returns a 4096-aligned block; an alignment of 0 or 3 succeeds and 100 gives a 128-aligned block, as the routine raises it to at least 32 and to the next power of two; an alignment of 2^63 or more returns null, keeps the block and leaves `errno` alone; and a size of 0 returns a block (the same one when it is already suitable) instead of freeing it. A block that is aligned and large enough is kept where it is, without giving back the bytes past the new size as the console's in-place resize does - [frexp](../../../core/libs/prx/libc/src/Math.cpp) (libc) - follows the two bundled PPSA03766/PPSA07923 functions: exponent-bit decomposition, conditional double-precision subnormal scaling, zero exponent for zero/NaN/infinity, and unchanged signaling NaNs. Masked-exception observations cover rounding, DAZ/FTZ and sticky flags; unmasked traps and other SDK versions are unverified. +- [Guest long double](../../../core/libs/prx/libc/include/GuestLongDouble.hpp) (lifted) - holds the guest's x87 80-bit value in a class whose arithmetic, square root, integer rounding and conversions run through the ps5lift runtime's x87, and whose comparisons, `frexp`, `scalbn`, `fmod`, `remainder` and parsing are exact; `asinl` and `acosl` run FreeBSD's msun ld80 code on the class, and the other transcendental `l` functions and `fmal` compute in the host's widest floating type, binary128 on Linux and `double` on macOS, and round the result to 80 bits, so on macOS they keep only 53 bits. Computing them with FreeBSD's msun ld80 code on the class would give the console's results. +- [__mulxc3 and __divxc3](../../../core/libs/prx/libc/src/FloatBuiltins.cpp) (lifted) - are not built in lifted builds, so a title that imports the long double complex multiply or divide helper gets no definition. +- [Long double scans](../../../core/libs/prx/libc/src/Formatting.cpp) (lifted) - `%La`, `%Le`, `%Lf` and `%Lg` store the guest's 80-bit long double, also when host code passes a pointer to its own `long double`. - [acosl](../../../core/libs/prx/libc/src/Math.cpp) (libc) runs at the caller's x87 precision, as the binary does, so on a guest thread that starts at 53 bits (see `asinl`) it returns the binary's 53-bit result, not the console's 64-bit one. It follows the `acosl` of the `libc.prx` bundled with PPSA21564 (0x3e5a0): FreeBSD msun's [`e_acosl.c`](https://github.com/freebsd/freebsd-src/blob/9f0432041b8fb69832f10c32da50a67f34feeb61/lib/msun/src/e_acosl.c) with the ld80 `invtrig.c` coefficients that `asinl` uses, where `_Feraise(FE_INVALID)` raises `FE_INVALID` and sets `errno` to `EDOM` for |x| > 1, the infinities and NaN before `(x - x) / (x - x)`. 20 million random arguments in (-1, 1) give the same bits as that code run natively on Linux at 64-, 53- and 24-bit precision; the host `acosl` differed on 1,603,469 of them at 64 bits. Other SDK versions and the system `libSceLibcInternal` are not compared - [atanl, sinl, cosl, tanl, sinhl, coshl, tanhl, expl, exp2l, logl, log10l and powl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, not by the console's FreeBSD msun ld80 code: results can differ from the console's in the last bit (glibc's `log10l(1000)` is not exactly 3), and on Linux glibc sets `errno` to `EDOM` or `ERANGE` where msun only raises the floating-point exception. `ldexpl` and `truncl` are exact operations and give the console's result; `logb` and `remainder` give the console's value too, but on Linux glibc sets `errno` (`ERANGE` for `logb(0)`, `EDOM` for `remainder` by zero or of an infinity) where msun only raises the floating-point exception - [Dinkumware atomics](../../../core/libs/prx/libc/src/DinkumwareAtomics.cpp) (libc) - every operation is sequentially consistent whatever memory order it is given, which is never weaker than the console. Matching the `libc.prx` bundled with PPSA08427 (that binary run side by side on every order from 0 to 7), `_Atomic_load_8` returns 0 for a release, acq_rel or invalid order and the 8-byte exchange and fetch operations return 0 without touching memory for an order above `memory_order_seq_cst`; the generic `_Atomic_copy`, `_Atomic_exchange` and `_Atomic_compare_exchange_*` lock the byte flag passed first, as the console does From 4478cbfaef40c977357d18f8aa42f59aa06e7022 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 09:01:47 +0000 Subject: [PATCH 27/31] feat(libkernel): build for a lifted guest Call guest thread entries and exception handlers through GuestCpu, and leave out the x86 assembly for sigsetjmp, siglongjmp, getcontext and their kin in lifted builds, where the runtime provides the jumps and context switching reports itself as not implemented. A handler raised on a lifted thread sees the guest registers of the thread's last call. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 3 ++ .../libs/prx/libkernel/Pthread/src/Thread.cpp | 16 +++++++ .../SyncOnAddress/src/SyncOnAddress.cpp | 7 +++ .../prx/libkernel/System/src/Exception.cpp | 44 +++++++++++++++++-- .../prx/libkernel/System/src/SignalSets.cpp | 2 + .../prx/libkernel/System/src/UserContext.cpp | 11 +++-- docs/dev/debt/libkernel.md | 4 ++ 7 files changed, 80 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp index f5d4eebb401b..370981b1dd3e 100644 --- a/core/libs/prx/libc/include/GuestCpu.hpp +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -22,6 +22,9 @@ const char* GuestCpuImportName_nid_no_patch(std::uint64_t address); std::uint64_t GuestCpuReadTsc_nid_no_patch(void); void GuestCpuCpuid_nid_no_patch(std::uint32_t leaf, std::uint32_t subleaf, std::uint32_t* registers); std::uint64_t GuestCpuXgetbv_nid_no_patch(std::uint32_t index); +std::uint64_t GuestCpuCall_nid_no_patch(std::uint64_t target, std::uint64_t first, std::uint64_t second); +void GuestCpuCallInterrupted_nid_no_patch(std::uint64_t target, std::uint64_t first, std::uint64_t second); +void GuestCpuInterruptedContext_nid_no_patch(std::uint64_t* registers, std::uint64_t* flags); } namespace GuestCpu { diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 0d9151719f64..301914b1eab4 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -14,6 +14,7 @@ #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/CpuTopology.hpp" +#include "prx/libc/include/GuestCpu.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include @@ -133,7 +134,9 @@ static void UnregisterGuestStackMapping(void* mapping) { guestStackMappings.erase(reinterpret_cast(mapping)); } +#ifndef ANYPS5_GUEST_LIFTED static constexpr std::uintptr_t GUEST_MAIN_STACK_TOP = 0x7eeffc000; +#endif static constexpr int GUEST_MAP_FIXED = 0x10; static std::atomic guestMainStackLow{0}; static std::atomic guestMainStackHigh{0}; @@ -227,6 +230,7 @@ static void ReleaseSpareStacks() { for (const auto& stack : release) ReleaseFinishedStack(stack); } +#ifndef ANYPS5_GUEST_LIFTED static std::size_t GuestMainStackBytes() { constexpr std::size_t fallback = std::size_t{8} << 20; constexpr std::size_t largest = std::size_t{1} << 30; @@ -235,9 +239,14 @@ static std::size_t GuestMainStackBytes() { const auto bytes = static_cast(std::min(stack.rlim_cur, largest)); return (bytes + PS5_PAGE_SIZE - 1) / PS5_PAGE_SIZE * PS5_PAGE_SIZE; } +#endif extern "C" [[noreturn]] void APS5_VABI EnterGuestMainStack_nid_no_patch(void* arguments, void (APS5_VABI* entry)(void*)) { if (entry == nullptr) throw std::invalid_argument("EnterGuestMainStack: null entry point"); +#ifdef ANYPS5_GUEST_LIFTED + entry(arguments); + throw std::runtime_error("EnterGuestMainStack: the guest entry returned"); +#else if (guestMainStackLow.load(std::memory_order_acquire) != 0) throw std::runtime_error("EnterGuestMainStack: the guest main stack was already entered"); std::uintptr_t arenaBase = 0; std::size_t arenaBytes = 0; @@ -276,6 +285,7 @@ extern "C" [[noreturn]] void APS5_VABI EnterGuestMainStack_nid_no_patch(void* ar : "r"(top), "D"(arguments), "a"(entry) : "memory"); __builtin_unreachable(); +#endif } static void AllocateGuestStack(PthreadPrivate* thread, std::size_t usableBytes) { @@ -867,6 +877,11 @@ void ThreadCancel::EndWait() { extern "C" void* APS5_VABI Aps5CallGuestEntry_nid_no_patch(PthreadEntry entry, void* arg); +#ifdef ANYPS5_GUEST_LIFTED +extern "C" void* APS5_VABI Aps5CallGuestEntry_nid_no_patch(PthreadEntry entry, void* arg) { + return reinterpret_cast(GuestCpuCall_nid_no_patch(reinterpret_cast(entry), reinterpret_cast(arg), 0)); +} +#else asm(".text\n" APS5_ASM_FUNCTION("Aps5CallGuestEntry_nid_no_patch") R"( .cfi_startproc @@ -889,6 +904,7 @@ asm(".text\n" ret .cfi_endproc )" APS5_ASM_FUNCTION_END("Aps5CallGuestEntry_nid_no_patch")); +#endif static void RunThread(std::unique_ptr args) { APS5_LOG_OUT("RunThread entry=0x%llx arg=%p self=%p", static_cast(reinterpret_cast(args->entry)), args->arg, static_cast(args->self)); diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index b8fbe7bf5d57..f1d372998a0d 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/Api.hpp" #include #include +#include #include #include #include @@ -187,7 +188,13 @@ template TValue LoadAddressValue(TValue* address) { if (reinterpret_cast(address) % alignof(TValue) == 0) return std::atomic_ref(*address).load(); TValue value; +#ifdef ANYPS5_GUEST_LIFTED + std::atomic_signal_fence(std::memory_order_seq_cst); + std::memcpy(&value, address, sizeof(value)); + std::atomic_thread_fence(std::memory_order_acquire); +#else asm volatile("mov %1, %0" : "=r"(value) : "m"(*reinterpret_cast(address))); +#endif return value; } diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index f468bbd5808d..e6c3a34b36f7 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -4,6 +4,7 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestCpu.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" #include "prx/libc/include/HostFault.hpp" #include "prx/libkernel/KernelErrors.hpp" @@ -236,7 +237,11 @@ void CallHandler(GuestExceptionHandler handler, int signum, GuestUcontext& ucont m.err = trap.error; const std::uint32_t mxcsrMask = FpWord(m, MxcsrMaskOffset); LogGuestExceptionContext(signum, m); +#ifdef ANYPS5_GUEST_LIFTED + GuestCpuCallInterrupted_nid_no_patch(reinterpret_cast(handler), static_cast(signum), reinterpret_cast(&ucontext)); +#else handler(signum, &ucontext); +#endif SetFpWord(m, MxcsrOffset, FpWord(m, MxcsrOffset) & (mxcsrMask != 0 ? mxcsrMask : DefaultMxcsrMask)); SetFpWord(m, MxcsrMaskOffset, mxcsrMask); } @@ -1095,6 +1100,32 @@ void InstallFaultDelivery() { constexpr int HostSignal = SIGUSR1; constexpr std::size_t FxsaveRegisterBytes = 416; +#ifdef ANYPS5_GUEST_LIFTED +void FromGuest(GuestMcontext& m) { + std::uint64_t registers[17]; + std::uint64_t flags = 0; + GuestCpuInterruptedContext_nid_no_patch(registers, &flags); + m.rax = registers[0]; + m.rdx = registers[1]; + m.rcx = registers[2]; + m.rbx = registers[3]; + m.rsi = registers[4]; + m.rdi = registers[5]; + m.rbp = registers[6]; + m.rsp = registers[7]; + m.r8 = registers[8]; + m.r9 = registers[9]; + m.r10 = registers[10]; + m.r11 = registers[11]; + m.r12 = registers[12]; + m.r13 = registers[13]; + m.r14 = registers[14]; + m.r15 = registers[15]; + m.rip = registers[16]; + m.rflags = flags; + m.len = sizeof(GuestMcontext); +} +#else void FromHost(const mcontext_t& host, GuestMcontext& m) { const auto* g = host.gregs; m.rdi = g[REG_RDI]; @@ -1144,6 +1175,7 @@ void ToHost(const GuestMcontext& m, mcontext_t& host) { g[REG_EFL] = static_cast(m.rflags); if (host.fpregs != nullptr) std::memcpy(host.fpregs, m.fpstate, FxsaveRegisterBytes); } +#endif void Deliver(int signum, siginfo_t*, void* context) { if (TimedWait::DeferDelivery(signum)) return; @@ -1154,14 +1186,20 @@ void Deliver(int signum, siginfo_t*, void* context) { (void)written; std::abort(); } - auto& host = static_cast(context)->uc_mcontext; GuestUcontext ucontext{}; +#ifdef ANYPS5_GUEST_LIFTED + static_cast(context); + FromGuest(ucontext.mcontext); + CallHandler(handler, RaisedSignal, ucontext); +#else + auto& host = static_cast(context)->uc_mcontext; FromHost(host, ucontext.mcontext); CallHandler(handler, RaisedSignal, ucontext); ToHost(ucontext.mcontext, host); +#endif } -#ifdef __linux__ +#if defined(__linux__) && !defined(ANYPS5_GUEST_LIFTED) constexpr std::array FaultSignals{SIGSEGV, SIGBUS, SIGILL, SIGFPE}; constexpr greg_t VectorDivide = 0; constexpr greg_t VectorInvalidOpcode = 6; @@ -1431,7 +1469,7 @@ int APS5_VABI sceKernelInstallExceptionHandler(int signum, void* handler) { if (!Allowed(signum) || handler == nullptr) return SCE_KERNEL_ERROR_EINVAL; void* expected = nullptr; if (!handlers[signum].compare_exchange_strong(expected, handler, std::memory_order_acq_rel)) return SCE_KERNEL_ERROR_EAGAIN; -#if defined(_WIN32) || defined(__linux__) +#if defined(_WIN32) || (defined(__linux__) && !defined(ANYPS5_GUEST_LIFTED)) if (FaultSignal(signum)) InstallFaultDelivery(); #endif return 0; diff --git a/core/libs/prx/libkernel/System/src/SignalSets.cpp b/core/libs/prx/libkernel/System/src/SignalSets.cpp index 8444422f5ddc..ddc5c90d0176 100644 --- a/core/libs/prx/libkernel/System/src/SignalSets.cpp +++ b/core/libs/prx/libkernel/System/src/SignalSets.cpp @@ -58,6 +58,7 @@ int APS5_VABI sigismember_nid_postfix(const GuestSignalSet* set, int signal) { return (set->bits[WordIndex(signal)] & Bit(signal)) != 0 ? 1 : 0; } } +#ifndef ANYPS5_GUEST_LIFTED #define APS5_SIGNAL_ASM_FUNCTION(name) APS5_ASM_FUNCTION(name) #if defined(_WIN32) || defined(__APPLE__) #define APS5_SIGNAL_ASM_CALL(name) " call " APS5_ASM_SYMBOL(name) "\n" @@ -133,3 +134,4 @@ asm(".text\n" "1:\n" " movq %rcx, 0(%rsp)\n" " ret\n"); +#endif diff --git a/core/libs/prx/libkernel/System/src/UserContext.cpp b/core/libs/prx/libkernel/System/src/UserContext.cpp index c98443056470..0254c2f8357c 100644 --- a/core/libs/prx/libkernel/System/src/UserContext.cpp +++ b/core/libs/prx/libkernel/System/src/UserContext.cpp @@ -8,13 +8,11 @@ #include #include #include -#ifdef __EMSCRIPTEN__ -#include "prx/libc/include/General.hpp" -#else +#if !defined(__EMSCRIPTEN__) && !defined(ANYPS5_GUEST_LIFTED) #include #endif -#ifdef __EMSCRIPTEN__ +#if defined(__EMSCRIPTEN__) || defined(ANYPS5_GUEST_LIFTED) extern "C" { int APS5_VABI getcontext_nid_postfix(UserUcontext* context) { @@ -33,6 +31,11 @@ int APS5_VABI swapcontext_nid_postfix(UserUcontext* saved, const UserUcontext* n NotImplemented_nid_no_patch(__func__); } +int SigreturnContext_nid_no_patch(const UserUcontext* context) { + static_cast(context); + NotImplemented_nid_no_patch(__func__); +} + } #else namespace { diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 47a808028549..5e7479479097 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -164,6 +164,10 @@ ### Functional - [Guest main stack](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - on Linux the title's entry point runs on a stack that ends at the address a payload's main thread stack ends at on a PS5 (firmware 13.60), `0x7eeffc000` (moved with the guest arena in ASan builds), with a 16 KiB guard below it. Its size is the host's `RLIMIT_STACK` (8 MiB when unlimited), not the console's: the `sceUserMainThreadStackSize` of the title's process parameters is not read, and the payload's stack was 2 MiB. A guest mapping already registered in that range stops the entry with an error that names the range instead of being replaced. Windows and macOS still enter the title on the host stack +- [EnterGuestMainStack_nid_no_patch](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - calls the guest entry on the guest stack the ps5lift runtime gave the main thread, not on a main stack at the console's address. +- [getcontext, setcontext, swapcontext, sigreturn](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - report themselves as not implemented, since they switch guest stacks. +- [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - the handler sees the registers of the target thread's last call, with `rip` its return address, not of the instruction it interrupted, and reads the context's `fpstate` (with the MXCSR and the x87 control word in it), `cs` and `ss` as zero; it runs on a stack of its own rather than below the interrupted frame, and its changes to the context are dropped. +- [sceKernelInstallExceptionHandler](../../../core/libs/prx/libkernel/System/src/Exception.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) delivers no host faults, as on macOS: a SIGSEGV, SIGBUS, SIGILL or SIGFPE in lifted code is left to the ps5lift runtime's or the host's own handling, because it happens in native code, whose registers and trap numbers are not the guest's. A handler sees only the exceptions `sceKernelRaiseException` sends. - [sceKernelGetModuleInfoForUnwind](../../../core/libs/prx/libkernel/Module/src/Module.cpp) (WebAssembly) throws because guest module unwind metadata is not implemented for linear memory; it does not query the host's process mappings - [scePthreadYield](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) on Windows calls Sleep(1) after 512 consecutive yields that find no other thread ready on the current processor and arrive less than ~10,000 TSC ticks apart (so the span varies with host CPU TSC rate); FreeBSD sched_yield never sleeps. - [__tls_get_addr](../../../core/libs/prx/libkernel/Module/src/Tls.cpp) (libkernel, NID `vNe1w4diLCs`) throws on macOS, where the relinker binds guest modules to `Aps5GuestTlsGetAddr_nid_no_patch` ([GuestTlsMacOs.cpp](../../../core/libs/prx/libkernel/Pthread/src/GuestTlsMacOs.cpp)) instead; it also throws for a null index, module 0 and a Windows thread without a block for the module. It reads the module word as the relinker writes it for `R_X86_64_DTPMOD64`: on Windows the address of the module's PE `_tls_index`, resolved through the thread's `ThreadLocalStoragePointer` as in [WindowsGuestStartup.cpp](../../../core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp); on Linux the glibc module ID, passed to glibc's `__tls_get_addr`. Guest modules' imports are bound to the host resolver, so this export is reached only through a lookup by NID. From caa4db32852c644048af82c6fd6ff6409058b2f8 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Wed, 28 Oct 2026 09:54:29 +0000 Subject: [PATCH 28/31] fix(libkernel): call a lifted thread entry as the native callback it is The bridge already turns the guest entry point into a native thunk, so a lifted build calls it directly instead of through a GuestCpu call. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 1 - core/libs/prx/libkernel/Pthread/src/Thread.cpp | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp index 370981b1dd3e..69ad956396a5 100644 --- a/core/libs/prx/libc/include/GuestCpu.hpp +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -22,7 +22,6 @@ const char* GuestCpuImportName_nid_no_patch(std::uint64_t address); std::uint64_t GuestCpuReadTsc_nid_no_patch(void); void GuestCpuCpuid_nid_no_patch(std::uint32_t leaf, std::uint32_t subleaf, std::uint32_t* registers); std::uint64_t GuestCpuXgetbv_nid_no_patch(std::uint32_t index); -std::uint64_t GuestCpuCall_nid_no_patch(std::uint64_t target, std::uint64_t first, std::uint64_t second); void GuestCpuCallInterrupted_nid_no_patch(std::uint64_t target, std::uint64_t first, std::uint64_t second); void GuestCpuInterruptedContext_nid_no_patch(std::uint64_t* registers, std::uint64_t* flags); } diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 301914b1eab4..ee8a02faca6b 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -14,7 +14,6 @@ #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/CpuTopology.hpp" -#include "prx/libc/include/GuestCpu.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include @@ -879,7 +878,7 @@ extern "C" void* APS5_VABI Aps5CallGuestEntry_nid_no_patch(PthreadEntry entry, v #ifdef ANYPS5_GUEST_LIFTED extern "C" void* APS5_VABI Aps5CallGuestEntry_nid_no_patch(PthreadEntry entry, void* arg) { - return reinterpret_cast(GuestCpuCall_nid_no_patch(reinterpret_cast(entry), reinterpret_cast(arg), 0)); + return entry(arg); } #else asm(".text\n" From 3a52434c86b0999fcb3c1a1e1c8bf64b0701c635 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Thu, 29 Oct 2026 01:37:12 +0000 Subject: [PATCH 29/31] feat(libkernel): run a lifted guest thread on the stack it asked for A lifted thread's guest frames lived on ps5lift's fixed 8 MiB stack while scePthreadCreate sized the host stack, which holds the lifted frames, from the guest's request and reported part of it as the thread's stack. The guest stack is now a guarded mapping of the size the guest asked for, which the thread hands to the runtime through GuestCpuUseStack before its entry runs and which scePthreadAttrGet reports, and the host stack is the size GuestCpuHostStackSize gives. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 2 ++ .../libs/prx/libkernel/Pthread/src/Thread.cpp | 30 +++++++++++++++---- docs/dev/debt/libkernel.md | 1 + 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp index 69ad956396a5..a5a3d53e91cc 100644 --- a/core/libs/prx/libc/include/GuestCpu.hpp +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -22,6 +22,8 @@ const char* GuestCpuImportName_nid_no_patch(std::uint64_t address); std::uint64_t GuestCpuReadTsc_nid_no_patch(void); void GuestCpuCpuid_nid_no_patch(std::uint32_t leaf, std::uint32_t subleaf, std::uint32_t* registers); std::uint64_t GuestCpuXgetbv_nid_no_patch(std::uint32_t index); +void GuestCpuUseStack_nid_no_patch(std::uint64_t stackTop); +std::uint64_t GuestCpuHostStackSize_nid_no_patch(void); void GuestCpuCallInterrupted_nid_no_patch(std::uint64_t target, std::uint64_t first, std::uint64_t second); void GuestCpuInterruptedContext_nid_no_patch(std::uint64_t* registers, std::uint64_t* flags); } diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index ee8a02faca6b..7a4a42003851 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -15,6 +15,7 @@ #include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/CpuTopology.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestCpu.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include #include @@ -775,7 +776,9 @@ static thread_local std::unique_ptr adoptedThread; // and aborts. scePthreadExit instead jumps back to the thread start routine, which, // like _endthreadex on Windows, skips the guest frames without unwinding them. static thread_local std::jmp_buf* threadExitJump = nullptr; +#endif +#if !defined(_WIN32) && !defined(ANYPS5_GUEST_LIFTED) static void* MeasureStackReserve(void* reserve) { std::uintptr_t low = 0; std::uintptr_t high = 0; @@ -913,7 +916,12 @@ static void RunThread(std::unique_ptr args) { BindHostCpuClock(self); TimedWait::BindThreadWaitState(&self->waitCount); CurrentGuestState().current = self; +#ifdef ANYPS5_GUEST_LIFTED + GuestCpuUseStack_nid_no_patch(reinterpret_cast(self->stackAddress) + self->stackSize); + GuestCpu::SetX87Control(GUEST_THREAD_FPU_CONTROL); +#else asm volatile("fldcw %0" : : "m"(GUEST_THREAD_FPU_CONTROL)); +#endif RegisterStack(self); #ifndef _WIN32 const sigset_t signalMask = args->signalMask; @@ -1097,12 +1105,16 @@ static void* StartNativeThread(void* opaque) { std::unique_ptr args(static_cast(opaque)); auto* self = args->guest->self; try { +#ifdef ANYPS5_GUEST_LIFTED + self->stackAddress = static_cast(self->guestStack) + self->guestStackBytes - self->stackSize; +#else std::uintptr_t low = 0; std::uintptr_t high = 0; const auto reserve = HostStackReserve(); if (!HostStackLimits(&low, &high) || high - low < reserve + self->stackSize) throw std::runtime_error("Cannot query guest thread stack"); self->stackAddress = reinterpret_cast(high - reserve - self->stackSize); +#endif self->threadId = std::this_thread::get_id(); #if defined(__linux__) self->hostTid = static_cast(gettid()); @@ -1192,17 +1204,21 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread if (detached) ReleaseThread(published); #else - const auto reserve = HostStackReserve(); +#ifdef ANYPS5_GUEST_LIFTED + const std::size_t hostFrames = 0; +#else + const std::size_t hostFrames = HostStackReserve() + HOST_STACK_MARGIN; +#endif const auto page = static_cast(sysconf(_SC_PAGESIZE)); - if (p->stackSize < 16384 || p->stackSize > std::numeric_limits::max() - reserve - HOST_STACK_MARGIN - page) + if (p->stackSize < 16384 || p->stackSize > std::numeric_limits::max() - hostFrames - page) throw std::runtime_error("scePthreadCreate: invalid stack size"); - const std::size_t nativeStack = (p->stackSize + reserve + HOST_STACK_MARGIN + page - 1) / page * page; + const std::size_t stackBytes = (p->stackSize + hostFrames + page - 1) / page * page; sigset_t creatorMask; pthread_sigmask(SIG_SETMASK, nullptr, &creatorMask); args->signalMask = creatorMask; #if defined(__linux__) ReapRetiredStacks(); - AllocateGuestStack(p.get(), nativeStack); + AllocateGuestStack(p.get(), stackBytes); #endif auto native = std::make_unique(NativeThreadArgs{std::move(args), start.get_future(), {}}); auto initialized = native->initialized.get_future(); @@ -1211,10 +1227,12 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread FreeGuestStack(p.get()); throw std::system_error(result, std::generic_category(), "Creating guest thread"); } -#if defined(__linux__) +#if defined(ANYPS5_GUEST_LIFTED) + int created = pthread_attr_setstacksize(&nativeAttr, GuestCpuHostStackSize_nid_no_patch()); +#elif defined(__linux__) int created = pthread_attr_setstack(&nativeAttr, static_cast(p->guestStack) + GUEST_STACK_GUARD, p->guestStackBytes - GUEST_STACK_GUARD); #else - int created = pthread_attr_setstacksize(&nativeAttr, nativeStack); + int created = pthread_attr_setstacksize(&nativeAttr, stackBytes); #endif if (created == 0) { sigset_t allSignals; diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 5e7479479097..21cac08d71e1 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -165,6 +165,7 @@ - [Guest main stack](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - on Linux the title's entry point runs on a stack that ends at the address a payload's main thread stack ends at on a PS5 (firmware 13.60), `0x7eeffc000` (moved with the guest arena in ASan builds), with a 16 KiB guard below it. Its size is the host's `RLIMIT_STACK` (8 MiB when unlimited), not the console's: the `sceUserMainThreadStackSize` of the title's process parameters is not read, and the payload's stack was 2 MiB. A guest mapping already registered in that range stops the entry with an error that names the range instead of being replaced. Windows and macOS still enter the title on the host stack - [EnterGuestMainStack_nid_no_patch](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - calls the guest entry on the guest stack the ps5lift runtime gave the main thread, not on a main stack at the console's address. +- [scePthreadAttrGet and sceKernelIsStack](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - for a thread `scePthreadCreate` did not start, the main thread and a host thread adopted by `scePthreadSelf`, report the host thread's stack instead of the guest stack the ps5lift runtime gave it, so `sceKernelIsStack` does not find an address on that guest stack. A thread `scePthreadCreate` started reports its guest stack. - [getcontext, setcontext, swapcontext, sigreturn](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - report themselves as not implemented, since they switch guest stacks. - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - the handler sees the registers of the target thread's last call, with `rip` its return address, not of the instruction it interrupted, and reads the context's `fpstate` (with the MXCSR and the x87 control word in it), `cs` and `ss` as zero; it runs on a stack of its own rather than below the interrupted frame, and its changes to the context are dropped. - [sceKernelInstallExceptionHandler](../../../core/libs/prx/libkernel/System/src/Exception.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) delivers no host faults, as on macOS: a SIGSEGV, SIGBUS, SIGILL or SIGFPE in lifted code is left to the ps5lift runtime's or the host's own handling, because it happens in native code, whose registers and trap numbers are not the guest's. A handler sees only the exceptions `sceKernelRaiseException` sends. From d37f240a01a47deb4bb084fa0778624585b9add9 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Sat, 31 Oct 2026 00:05:02 +0000 Subject: [PATCH 30/31] fix(libkernel): allocate a lifted guest's thread objects in guest memory A thread handle is the address of its PthreadPrivate, which came from the host heap. On the console it is the address of the thread's structure in the process's own memory. On an aarch64 host the host heap lies at 0xaaaa... and 0xffff..., outside anything the guest can address, and a library that prints a handle with %p and reads it back with base 0 saw a leading hex letter and threw where the console's handle starts with a digit. In lifted builds PthreadPrivate now comes from the guest heap, so every handle, those of adopted threads included, is a guest address. Direct builds keep the host heap. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/Pthread/include/Pthread.hpp | 4 ++++ core/libs/prx/libkernel/Pthread/src/Thread.cpp | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 0f1c2d83fddb..b9bbde8f79b3 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -180,6 +180,10 @@ struct PthreadPrivate { ~PthreadPrivate(); PthreadPrivate(const PthreadPrivate&) = delete; PthreadPrivate& operator=(const PthreadPrivate&) = delete; +#ifdef ANYPS5_GUEST_LIFTED + static void* operator new(std::size_t bytes); + static void operator delete(void* pointer); +#endif }; bool GuestThreadStack(std::uintptr_t address, std::uintptr_t* start, std::uintptr_t* end); diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 7a4a42003851..d7d2df3b221d 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -16,6 +16,9 @@ #include "prx/libc/include/CpuTopology.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestCpu.hpp" +#ifdef ANYPS5_GUEST_LIFTED +#include "prx/libc/include/GuestHeap.hpp" +#endif #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include #include @@ -572,6 +575,16 @@ static CpuClockRegistry& CpuClocks() { return *registry; } +#ifdef ANYPS5_GUEST_LIFTED +void* PthreadPrivate::operator new(std::size_t bytes) { + return GuestHeap::GuestHeapAlign_nid_postfix(alignof(PthreadPrivate), bytes); +} + +void PthreadPrivate::operator delete(void* pointer) { + GuestHeap::GuestHeapFree_nid_postfix(pointer); +} +#endif + PthreadPrivate::PthreadPrivate(const PthreadAttrPrivate* attr) : _finished(false), _retval(nullptr), _detached(false), _adopted(false), _handleStorage(nullptr), _joined(false) { auto& registry = CpuClocks(); std::lock_guard lock(registry.lock); From df80f50f149746b6722fb5212442c5e043b13b58 Mon Sep 17 00:00:00 2001 From: oberonlift Date: Sun, 1 Nov 2026 15:44:23 +0000 Subject: [PATCH 31/31] feat(libkernel): report guest ranges made writable to the lifted runtime ps5lift loads from a lifted image's read-only segments without ordering, which holds only while nothing writes them. In lifted builds DoMprotect reports the pages each protection change makes writable to GuestCpuMakingWritable_nid_no_patch, and the runtime stops the program when they overlap such a segment. A MAP_FIXED mapping over such a segment is not reported; the libkernel debt notes record it. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libc/include/GuestCpu.hpp | 1 + core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp | 6 ++++++ docs/dev/debt/libkernel.md | 1 + 3 files changed, 8 insertions(+) diff --git a/core/libs/prx/libc/include/GuestCpu.hpp b/core/libs/prx/libc/include/GuestCpu.hpp index a5a3d53e91cc..0adaed85bd39 100644 --- a/core/libs/prx/libc/include/GuestCpu.hpp +++ b/core/libs/prx/libc/include/GuestCpu.hpp @@ -18,6 +18,7 @@ void GuestCpuX87Wait_nid_no_patch(void); std::uint32_t GuestCpuMxcsrMask_nid_no_patch(void); void GuestCpuUnwindStart_nid_no_patch(std::uint64_t* registers); const char* GuestCpuImportName_nid_no_patch(std::uint64_t address); +void GuestCpuMakingWritable_nid_no_patch(std::uint64_t address, std::uint64_t length); [[noreturn]] void GuestCpuUnwindResume_nid_no_patch(const std::uint64_t* registers); std::uint64_t GuestCpuReadTsc_nid_no_patch(void); void GuestCpuCpuid_nid_no_patch(std::uint32_t leaf, std::uint32_t subleaf, std::uint32_t* registers); diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 0b4c50314203..238c238b7151 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -2,6 +2,9 @@ #include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestArena.hpp" +#ifdef ANYPS5_GUEST_LIFTED +#include "prx/libc/include/GuestCpu.hpp" +#endif #include "prx/libc/include/GuestWriteWatch.hpp" #include #include @@ -1495,6 +1498,9 @@ int DoMprotect(const void* addr, size_t len, int prot, bool requireMappedStart) auto bytes = static_cast(end - first); const void* pointer = reinterpret_cast(first); const auto nativeProtection = LinuxProtFromSce(prot); +#ifdef ANYPS5_GUEST_LIFTED + if ((nativeProtection & PROT_WRITE) != 0) GuestCpuMakingWritable_nid_no_patch(first, bytes); +#endif if (GuestThreadStackHostRangeOverlaps(address, address + len)) throw std::system_error(EACCES, std::generic_category(), "mprotect overlaps host-only guest thread stack pages"); std::uintptr_t stackStart = 0; diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 21cac08d71e1..2c8bd42fbdf5 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -169,6 +169,7 @@ - [getcontext, setcontext, swapcontext, sigreturn](../../../core/libs/prx/libkernel/System/src/UserContext.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - report themselves as not implemented, since they switch guest stacks. - [sceKernelRaiseException](../../../core/libs/prx/libkernel/System/src/Exception.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - the handler sees the registers of the target thread's last call, with `rip` its return address, not of the instruction it interrupted, and reads the context's `fpstate` (with the MXCSR and the x87 control word in it), `cs` and `ss` as zero; it runs on a stack of its own rather than below the interrupted frame, and its changes to the context are dropped. - [sceKernelInstallExceptionHandler](../../../core/libs/prx/libkernel/System/src/Exception.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) delivers no host faults, as on macOS: a SIGSEGV, SIGBUS, SIGILL or SIGFPE in lifted code is left to the ps5lift runtime's or the host's own handling, because it happens in native code, whose registers and trap numbers are not the guest's. A handler sees only the exceptions `sceKernelRaiseException` sends. +- [DoMprotect and fixed mappings](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel, `ANYPS5_GUEST_LIFTED` builds) - only a protection change reports the pages it makes writable to the ps5lift runtime, which stops the program when they overlap a lifted image's read-only segment, since ps5lift loads from those segments without ordering. A `MAP_FIXED` flexible, direct, shared or anonymous mapping over such a segment is not reported, and the program runs on with those loads unordered. - [sceKernelGetModuleInfoForUnwind](../../../core/libs/prx/libkernel/Module/src/Module.cpp) (WebAssembly) throws because guest module unwind metadata is not implemented for linear memory; it does not query the host's process mappings - [scePthreadYield](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) on Windows calls Sleep(1) after 512 consecutive yields that find no other thread ready on the current processor and arrive less than ~10,000 TSC ticks apart (so the span varies with host CPU TSC rate); FreeBSD sched_yield never sleeps. - [__tls_get_addr](../../../core/libs/prx/libkernel/Module/src/Tls.cpp) (libkernel, NID `vNe1w4diLCs`) throws on macOS, where the relinker binds guest modules to `Aps5GuestTlsGetAddr_nid_no_patch` ([GuestTlsMacOs.cpp](../../../core/libs/prx/libkernel/Pthread/src/GuestTlsMacOs.cpp)) instead; it also throws for a null index, module 0 and a Windows thread without a block for the module. It reads the module word as the relinker writes it for `R_X86_64_DTPMOD64`: on Windows the address of the module's PE `_tls_index`, resolved through the thread's `ThreadLocalStoragePointer` as in [WindowsGuestStartup.cpp](../../../core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp); on Linux the glibc module ID, passed to glibc's `__tls_get_addr`. Guest modules' imports are bound to the host resolver, so this export is reached only through a lookup by NID.