From 60f3dcc5dbe987ab75d3a980cd54b1aa9d93c1e5 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Sat, 24 Oct 2026 08:27:00 -0400 Subject: [PATCH 1/2] test(libc): check acoshf domain errno and exception flags --- core/libs/tests/GuestMath.cpp | 54 ++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index 8297d06458..5f7073e022 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -983,11 +983,62 @@ static void CheckHyperbolic() { Require(Near(acosh_nid_postfix(1e300), 691.4686750787736, 1e-15)); } +static void CheckAcoshfDomainEnvironment() { + const RestoreFloatingPointState saved; + const unsigned savedCsr = _mm_getcsr(); + const struct { + std::uint32_t input; + std::uint32_t result; + unsigned raised; + unsigned x87Raised; + int error; + } cases[] = { + {0x00000000u, 0xffc00000u, 1u, 1u, EDOM}, + {0x80000000u, 0xffc00000u, 1u, 1u, EDOM}, + {0x3f000000u, 0xffc00000u, 1u, 1u, EDOM}, + {0x3f7fffffu, 0xffc00000u, 1u, 1u, EDOM}, + {0xbf800000u, 0xffc00000u, 1u, 1u, EDOM}, + {0xff800000u, 0xffc00000u, 1u, 1u, EDOM}, + {0xffc00001u, 0xffc00001u, 1u, 1u, EDOM}, + {0xff800001u, 0xffc00001u, 1u, 1u, EDOM}, + {0x3f800000u, 0x00000000u, 0u, 0u, 1234}, + {0x7f800000u, 0x7f800000u, 0u, 0u, 1234}, + {0x7fc00001u, 0x7fc00001u, 0u, 0u, 1234}, + {0x7f800001u, 0x7fc00001u, 1u, 0u, 1234}, + }; + const int rounding[] = {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}; + float (APS5_VABI * volatile call)(float) = acoshf_nid_postfix; + for (unsigned mode = 0; mode != 4; ++mode) { + for (unsigned denorm = 0; denorm != 4; ++denorm) { + const unsigned control = 0x1f80u | (mode << 13u) | ((denorm & 1u) ? 0x40u : 0u) | ((denorm & 2u) ? 0x8000u : 0u); + for (std::uint16_t sticky : {0u, 5u, 32u}) { + for (const auto& item : cases) { + Require(std::fesetenv(FE_DFL_ENV) == 0 && std::fesetround(rounding[mode]) == 0); + Require(fesetexceptflag_nid_postfix(&sticky, 0x3f) == 0); + _mm_setcsr(control | sticky); + GuestFloatingPointEnvironment before; + Require(fegetenv_nid_postfix(&before) == 0 && (before.status & 0x3fu) == sticky); + errno = 1234; + const auto result = std::bit_cast(call(std::bit_cast(item.input))); + const int error = errno; + GuestFloatingPointEnvironment after; + Require(fegetenv_nid_postfix(&after) == 0); + if (result != item.result || after.mxcsr != (control | sticky | item.raised) || (after.status & 0x3fu) != (sticky | item.x87Raised) || after.control != before.control || error != item.error) { + std::fprintf(stderr, "acoshf domain: input=%08llx mode=%u denorm=%u sticky=%u bits=%08llx csr=%x x87=%x errno=%d\n", static_cast(item.input), mode, denorm, sticky, static_cast(result), after.mxcsr, after.status, error); + Require(false); + } + } + } + } + } + _mm_setcsr(savedCsr); +} + static void CheckAcoshfMsunBoundaries() { Require(acoshf_nid_postfix(1.0f) == 0.0f && !std::signbit(acoshf_nid_postfix(1.0f))); errno = EINTR; std::feclearexcept(FE_ALL_EXCEPT); - Require(std::isnan(acoshf_nid_postfix(0.5f)) && errno == EINTR && (std::fetestexcept(FE_INVALID) & FE_INVALID) != 0); + Require(std::isnan(acoshf_nid_postfix(0.5f)) && errno == EDOM && (std::fetestexcept(FE_INVALID) & FE_INVALID) != 0); for (const std::uint32_t bits : {0x3f800001u, 0x3fffffffu, 0x40000000u, 0x40000001u, 0x4d7fffffu, 0x4d800000u, 0x4d800001u, 0x7f7fffffu}) { const float x = std::bit_cast(bits); @@ -1845,6 +1896,7 @@ int main() { Require(std::isinf(__powidf2_nid_postfix(-0.0, -3)) && std::signbit(__powidf2_nid_postfix(-0.0, -3))); Require(__powidf2_nid_postfix(2.0, -127) == 0x1p-127); CheckHyperbolic(); + CheckAcoshfDomainEnvironment(); CheckAcoshfMsunBoundaries(); CheckCoshfMsunBoundaries(); CheckExp2fConsoleBits(); From ad111b3ce18e101010f49f0715be9a0d4b685248 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Sat, 24 Oct 2026 08:28:59 -0400 Subject: [PATCH 2/2] fix(libc): preserve acoshf domain errno and exception flags --- core/libs/prx/libc/src/Math.cpp | 6 +++++- docs/dev/debt/libc.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index d88fc6d00b..aee00ca2fd 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1062,7 +1062,11 @@ float APS5_VABI acoshf_nid_postfix(float x) { constexpr float ln2 = 6.9314718246e-01f; const std::uint32_t bits = std::bit_cast(x); const std::uint32_t magnitude = bits & 0x7fffffffu; - if ((bits & 0x80000000u) != 0 || magnitude < 0x3f800000u) return (x - x) / (x - x); + if ((bits & 0x80000000u) != 0 || magnitude < 0x3f800000u) { + _Feraise_nid_postfix(0x01); + errno = EDOM; + return (x - x) / (x - x); + } if (magnitude >= 0x7f800000u) return x + x; if (magnitude == 0x3f800000u) return 0.0f; if (magnitude >= 0x4d800000u) return logf_nid_postfix(x) + ln2; diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index c648774059..737d9be725 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -115,7 +115,7 @@ - [log1p](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not compare floating-point exceptions, and its double inputs are checked by sampling, not exhaustively. It is FreeBSD msun's [`s_log1p.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_log1p.c). As the titles' `libc.prx` does, it sets `EDOM` at msun's NaN return (arguments below -1, `-inf` and negative NaNs) and leaves `errno` alone for `log1p(-1)`, where it returns `-inf`. It gives the same bits, NaN payloads included, and `errno` as the `log1p` bundled with PPSA07923 and PPSA05916 on 320 million random and 68,216 structured arguments on a Windows host. The FMA-contracted build bundled with PPSA13934 differs on 73,682 of the random ones. - [nextafter and nextafterf](../../../core/libs/prx/libc/src/Math.cpp) (libc) do not compare floating-point exceptions, and their inputs are checked by sampling, not exhaustively. They are FreeBSD msun's [`s_nextafter.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_nextafter.c) and [`s_nextafterf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/s_nextafterf.c), with `ERANGE` at msun's overflow and underflow returns as the titles' `libc.prx` sets it; a step away from zero returns early and sets none. The copies bundled with eleven titles give the same bits and `errno` on 4,000,784 sampled pairs per function on a Windows host. - [sinhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `sinhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_sinhf.c`](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_sinhf.c) from before `__ldexp_expf`, which squares `expf(|x| / 2)` between `logf(FLT_MAX)` and the overflow threshold, calling msun's [`expm1f`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_expm1f.c) and [`expf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_expf.c), whose paths for the arguments it passes are inlined here. Past the threshold Sony's error helper sets `errno` to `ERANGE` before returning `x * 1e37`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits as this implementation for every finite argument up to the threshold, of both signs; it was not run on Linux. That the system `libSceLibcInternal`, which Ruffle (PPSA68091) and RetroArch (PPSA99169) import while bundling a zero-return `libc.prx`, has the same code is assumed -- [acoshf](../../../core/libs/prx/libc/src/Math.cpp) (libc) does not reproduce bundled invalid-domain errno or x87 exception flags (#10610); exact system `libSceLibcInternal` behavior and FMA-contracted copies remain unverified. Positive finite inputs use [msun](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_acoshf.c) range handling and this file's `logf`; the middle range divides -1 before addition under directed rounding. Bits, errno and SSE/x87 status and controls match the supplied PPSA03766/PPSA07923 copies on 236,592 positive finite calls (4,929 inputs, four rounding modes, DAZ/FTZ combinations and sticky flags); these samples are not exhaustive. +- [acoshf](../../../core/libs/prx/libc/src/Math.cpp) (libc) exact system `libSceLibcInternal` behavior and FMA-contracted copies remain unverified. Positive finite inputs use [msun](https://github.com/freebsd/freebsd-src/blob/5aa554c7e5b6fd9042986df07f12ac5687e41392/lib/msun/src/e_acoshf.c) range handling and this file's `logf`; the middle range divides -1 before addition under directed rounding. Bits, errno and SSE/x87 status and controls match the supplied PPSA03766/PPSA07923 copies on 236,592 positive finite calls (4,929 inputs, four rounding modes, DAZ/FTZ combinations and sticky flags); these samples are not exhaustive. Invalid-domain errno and SSE/x87 invalid flags are checked against two supplied bundled copies; those samples are not exhaustive either. - [acoshl, asinhl, atanhl, atan2l, cbrtl, erfl, erfcl, expm1l, hypotl, log1pl, log2l, lgammal, tgammal, j0f, j1f, jnf, y0f, y1f and ynf](../../../core/libs/prx/libc/src/Math.cpp) (libc) return the host C library's results (the float Bessel functions through its double ones), not those of the console's own code, so the last bits can differ (`y0f`, `y1f`, `ynf` follow the console at `+-0`). As in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x56410), `lgammal` does not store the sign in `signgam`; `hypotl` returns infinity for an infinite argument even when the other is a NaN, as C11 F.10.4.3 requires. [hypot3, hypot3f and hypot3l](../../../core/libs/prx/libc/src/Math.cpp) follow that module (0x51D30, 0x520D0, 0x52420): the first NaN of x, y and z is returned unchanged, any infinity gives positive infinity, a zero argument gives the two-argument hypot of the other two, and otherwise the largest magnitude is combined with the hypot of the other two - [asinl](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `asinl` of the `libc.prx` bundled with PPSA28319 (disassembly), FreeBSD msun's [`e_asinl.c`](https://github.com/freebsd/freebsd-src/blob/9f0432041b8fb69832f10c32da50a67f34feeb61/lib/msun/src/e_asinl.c) with the ld80 [`invtrig.c`](https://github.com/freebsd/freebsd-src/blob/9f0432041b8fb69832f10c32da50a67f34feeb61/lib/msun/ld80/invtrig.c) coefficients, which the binary holds as the same 80-bit constants. For |x| > 1, the infinities and NaN, Sony's error helper raises `FE_INVALID` and sets `errno` to `EDOM` before returning `(x - x) / (x - x)`, so a NaN argument also sets `EDOM`; msun itself sets no `errno`. 20 million random arguments in (-1, 1) give the same bits as that code run on a Windows host. The result needs the x87 precision control at 64 bits, as FreeBSD amd64 starts threads (control word `0x37f`), but guest threads do not set it: on Windows a thread started by `_beginthreadex` runs at 53 bits (`0x27f`), where `asinl(0.75)` returns mantissa `0xd91a98ae3406e000` instead of `0xd91a98ae3406e041`. The system `libSceLibcInternal` that RetroArch (PPSA99169) imports is assumed to have the same code - [acosf](../../../core/libs/prx/libc/src/Math.cpp) (libc) - for |x| > 1 and NaN the console stores `EDOM` and raises `FE_INVALID` before returning `(x - x) / (x - x)`; here those return the same NaNs and leave `errno` as it was (#3688). For |x| <= 1 it follows the `acosf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_acosf.c`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_acosf.c), with the same constants, `pio2_lo` read at run time and the `0xfffff000` mask on `sqrtf(z)`, which the binary holds. Titles ship different builds of that `libc.prx`: PPSA13934's is FMA-contracted and differs from this one by 1 ulp on 129,170 inputs (#9668), so no single port matches every title's last bit.