Merge pull request #10610 from JJenkx/fix/libc-acoshf-domain-fenv

fix(libc): preserve acoshf domain errno and exception flags
This commit is contained in:
Dean authored and GitHub committed 2026-10-26 20:14:11 +13:00
commit 3d2b7c0673
3 files changed
+59 -3

No files matched your search

+5 -1
View File
@@ -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<std::uint32_t>(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;
+53 -1
View File
@@ -1003,11 +1003,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<std::uint32_t>(call(std::bit_cast<float>(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<unsigned long long>(item.input), mode, denorm, sticky, static_cast<unsigned long long>(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<float>(bits);
@@ -1866,6 +1917,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();
+1 -1
View File
@@ -119,7 +119,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.