diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index ebb046b03..e86fbda4e 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -45,12 +46,39 @@ auto ParseAsciiPrefix(const char16_t* text, char16_t** end, TParse parse) { return value; } +bool IsNanSubject(const char* text) { + while (*text == ' ' || (*text >= '\t' && *text <= '\r')) ++text; + if (*text == '+' || *text == '-') ++text; + return *text == 'n' || *text == 'N'; +} + +double QuietNan(double value, const char* text) { + const auto bits = std::bit_cast(value); + if ((bits & 0x7ff0000000000000u) != 0x7ff0000000000000u || !IsNanSubject(text)) return value; + return std::bit_cast((bits & 0x8000000000000000u) | 0x7ff8000000000000u); +} + +float QuietNan(float value, const char* text) { + const auto bits = std::bit_cast(value); + if ((bits & 0x7f800000u) != 0x7f800000u || !IsNanSubject(text)) return value; + return std::bit_cast((bits & 0x80000000u) | 0x7fc00000u); +} + +long double QuietNan(long double value, const char* text) { + std::uint16_t exponent; + std::memcpy(&exponent, reinterpret_cast(&value) + sizeof(std::uint64_t), sizeof(exponent)); + if ((exponent & 0x7fffu) != 0x7fffu || !IsNanSubject(text)) return value; + const std::uint64_t significand = 0xc000000000000000u; + std::memcpy(&value, &significand, sizeof(significand)); + return value; +} + template TFloat ParseFloat(const char* text, char** end, TFloat (*parse)(const char*, char**)) { char* parsedEnd = nullptr; const TFloat value = parse(text, &parsedEnd); if (end != nullptr) *end = parsedEnd; - return parsedEnd == text ? TFloat{0} : value; + return parsedEnd == text ? TFloat{0} : QuietNan(value, text); } } diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 5a8e85f5f..4e7d9b6f7 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -148,6 +148,45 @@ static bool ExtendedBits(long double value, std::uint64_t mantissa, std::uint16_ return actualMantissa == mantissa && actualSignExponent == signExponent; } +static void CheckNanConversions() { + const struct { const char* text; std::size_t consumed; bool negative; } cases[] = { + {"nan(0x123)", 10, false}, {" -NaN(1)!", 8, true}, {"nan(0x800000)", 13, false}, + {"nan(0x10000000000000)", 21, false}, {"nan(0x8000000000000000)", 23, false}, + }; + bool correct = true; + for (const auto& test : cases) { + const char* expectedEnd = test.text + test.consumed; + const std::uint64_t expectedDouble = test.negative ? 0xfff8000000000000ull : 0x7ff8000000000000ull; + char* end = nullptr; + const auto value = std::bit_cast(strtod_nid_postfix(test.text, &end)); + if (value != expectedDouble || end != expectedEnd) { + std::fprintf(stderr, "strtod(\"%s\") = 0x%016llx\n", test.text, static_cast(value)); + correct = false; + } + const auto ascii = std::bit_cast(atof_nid_postfix(test.text)); + if (ascii != expectedDouble) { + std::fprintf(stderr, "atof(\"%s\") = 0x%016llx\n", test.text, static_cast(ascii)); + correct = false; + } + const auto single = std::bit_cast(strtof_nid_postfix(test.text, &end)); + if (single != (test.negative ? 0xffc00000u : 0x7fc00000u) || end != expectedEnd) { + std::fprintf(stderr, "strtof(\"%s\") = 0x%08x\n", test.text, static_cast(single)); + correct = false; + } + const long double extended = strtold_nid_postfix(test.text, &end); + if (!ExtendedBits(extended, 0xc000000000000000ull, test.negative ? 0xffff : 0x7fff) || end != expectedEnd) { + std::uint64_t significand = 0; + std::uint16_t signExponent = 0; + std::memcpy(&significand, &extended, sizeof(significand)); + std::memcpy(&signExponent, reinterpret_cast(&extended) + sizeof(significand), sizeof(signExponent)); + std::fprintf(stderr, "strtold(\"%s\") = 0x%04x'%016llx\n", test.text, static_cast(signExponent), + static_cast(significand)); + correct = false; + } + } + Require(correct); +} + static void CheckHypotSpecialValue() { Require(std::bit_cast(hypot_nid_postfix(INFINITY, std::bit_cast(0x7ff0000000000001ull))) == 0x7ff0000000000000ull); Require(std::bit_cast(hypot_nid_postfix(std::bit_cast(0x7ff0000000000001ull), INFINITY)) == 0x7ff0000000000000ull); @@ -609,6 +648,7 @@ int main() { Require(std::isinf(strtof_nid_postfix("1e1000", nullptr))); Require(*__error_nid_postfix() == 34); Require(strtold_nid_postfix("1.0000000000000000001!", &end) > 1.L && *end == '!'); + CheckNanConversions(); Require(fmodf_nid_postfix(5.5f, 2.f) == 1.5f); Require(fmodf_nid_postfix(-5.5f, 2.f) == -1.5f); Require(std::signbit(fmodf_nid_postfix(-4.f, 2.f))); diff --git a/core/libs/tests/GuestWideStrings.cpp b/core/libs/tests/GuestWideStrings.cpp index 64dd0afc3..45817eb8c 100644 --- a/core/libs/tests/GuestWideStrings.cpp +++ b/core/libs/tests/GuestWideStrings.cpp @@ -1,7 +1,11 @@ #include "prx/libc/include/general/VabiMacros.hpp" +#include #include +#include #include +#include #include +#include #include extern "C" { @@ -173,6 +177,13 @@ int main() { require(wcstof_nid_postfix(u"0.25", nullptr) == 0.25f); const char16_t* half = u"-1.5é"; require(wcstold_nid_postfix(half, &end) == -1.5L && end == half + 4); + const char16_t* wideNan = u" -nan(0x123)é"; + require(std::bit_cast(wcstod_nid_postfix(wideNan, &end)) == 0xfff8000000000000u && end == wideNan + 12); + require(std::bit_cast(wcstof_nid_postfix(wideNan, &end)) == 0xffc00000u && end == wideNan + 12); + const long double wideExtended = wcstold_nid_postfix(wideNan, &end); + std::uint64_t significand; + std::memcpy(&significand, &wideExtended, sizeof(significand)); + require(std::isnan(wideExtended) && std::signbit(wideExtended) && significand == 0xc000000000000000u && end == wideNan + 12); require(wcscoll_nid_postfix(u"a", u"b") < 0); require(wcscoll_nid_postfix(u"￿", u"a") > 0); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index b2f878ee6..dd4d63696 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -444,6 +444,7 @@ Throughout the project, every function at every stage either **does exactly what - [/dev/random and /dev/urandom](../../core/libs/prx/libkernel/File/src/RandomDevice.cpp) (libkernel) opened for reading return a descriptor backed by the host null device whose reads are filled with host OS entropy (`getentropy`, `rand_s` on Windows); both paths behave the same, never block, and positioned reads ignore the offset (FreeBSD `random(4)`). Only the exact paths are recognised, a `dup`ed descriptor reads as an empty file, `fstat`, `lseek`, `mmap` and `select` see the null device, and opening them for writing throws - [Wide-character conversions](../../core/libs/prx/libc/src/Multibyte.cpp) (libc) - The wide `wcsto*` functions parse the ASCII prefix of the guest string, as the C locale accepts only ASCII white space and digits. [`wcsrtombs`](../../core/libs/prx/libc/src/Multibyte.cpp) uses the guest's single-byte C locale through `wcrtomb`; other encodings and shift states are not supported - [realpath](../../core/libs/prx/libc/src/RealPath.cpp) (libc) follows FreeBSD 11's [`realpath.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/libc/stdlib/realpath.c) except for symlinks. The console's guest filesystem has none, so a host symlink under the guest root is followed for existence and type but not expanded, and `..` drops the previous component lexically, as `Resolve` does for every guest path. Through a host link `linkdir -> sub/deep`, `linkdir/../x` gives `x` beside the link, not beside its target. +- [strtod, strtof, strtold](../../core/libs/prx/libc/src/Strings.cpp) (libc) - `NAN(n-char-sequence)` converts to the quiet NaN that `nan` and `nanf` return, negated after a minus sign, and ignores the sequence as they ignore their tag. This is inferred from C17 7.12.11.2p2, which makes `nan("n")` equivalent to `strtod("NAN(n)", NULL)`, and not observed on the console. No console `nanl` was observed, so `strtold` returns the x87 default quiet NaN (exponent `0x7fff`, significand `0xc000000000000000`) - [nan and nanf](../../core/libs/prx/libc/src/Math.cpp) (libc) return fixed quiet NaN values (`0x7fc00000` and `0x7ff8000000000000`), ignoring the tag argument as observed in the console's `libc.prx` (both exports load a global constant via `mov rax,[rip+..]; vmovsd xmm0,[rax]; ret`); VLC (PPSA85300) stops on nanf at start-up after strsep. - [Draw input snapshots](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver): `PrepareDrawBindings` caps read-only input buffer snapshots at 64 MiB (overridable via `APS5_DRAW_SNAPSHOT_MAX_MIB`). Larger inputs (streaming heaps, bindless pools) or draws exceeding the cumulative budget skip the per-draw snapshot and bind in place, avoiding multi-gigabyte CPU copy stalls (such as Ratchet & Clank: Rift Apart PPSA01474 stalling at frame ~380 copying 36 GB every 5 s). If the title rewrites those large ranges on the CPU before GPU completion, the in-place binding observes the modification, as verified by `drawSnapshotCapTests`. - [pthread_attr_getinheritsched, pthread_attr_setinheritsched, pthread_attr_getscope, pthread_attr_setscope, pthread_attr_getstackaddr](../../core/libs/prx/libkernel/Pthread/Posix/Attr.cpp) (libkernel) follow FreeBSD 11 libthr ([thr_attr.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/lib/libthr/thread/thr_attr.c), [thr_create.c](https://github.com/freebsd/freebsd-src/blob/5ed7eb0d97ba4436218e810f61bd059acba984c2/lib/libthr/thread/thr_create.c)), not measured on the console: the scope defaults to `PTHREAD_SCOPE_SYSTEM` (2), only it and `PTHREAD_SCOPE_PROCESS` (0) are accepted and other values return `EINVAL`, and an inherit value other than `PTHREAD_INHERIT_SCHED` (4) or `PTHREAD_EXPLICIT_SCHED` (0) returns `ENOTSUP`. `pthread_attr_getstackaddr` returns the configured stack address pointer (null by default). A null output of `pthread_attr_getinheritsched` throws, since libthr writes through it unchecked. A thread keeps the scope it was created with, taken from the creating thread under `PTHREAD_INHERIT_SCHED`, and `pthread_attr_get_np` and `scePthreadAttrGet` report it, but every guest thread is a host thread at system scope, so process scope changes nothing