Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion core/libs/prx/libc/src/Math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,24 @@ long double AsinDenominator(long double t) {
return 1.0L + t * (q[0] + t * (q[1] + t * (q[2] + t * (q[3] + t * q[4]))));
}

int GammaSign(double x) {
const std::uint32_t high = static_cast<std::uint32_t>(std::bit_cast<std::uint64_t>(x) >> 32);
const std::uint32_t magnitude = high & 0x7fffffffu;
if (magnitude >= 0x7ff00000u) return 1;
const int direction = 1 - 2 * static_cast<int>(high >> 31);
if (magnitude < 0x3c700000u) return direction;
if (high < 0x80000000u) return 1;
if (magnitude >= 0x43300000u) return 1;
if (std::trunc(x) == x) return 1;
return static_cast<std::int64_t>(std::ceil(x)) % 2 == 0 ? -1 : 1;
}

}

extern "C" {

int signgam_nid_postfix = 0;

std::lldiv_t APS5_VABI lldiv_nid_postfix(long long numerator, long long denominator) {
return std::lldiv(numerator, denominator);
}
Expand Down Expand Up @@ -300,7 +314,10 @@ float APS5_VABI log1pf_nid_postfix(float x) { return std::log1p(x); }
double APS5_VABI expm1_nid_postfix(double x) { return std::expm1(x); }
float APS5_VABI expm1f_nid_postfix(float x) { return std::expm1(x); }
float APS5_VABI erff_nid_postfix(float x) { return std::erf(x); }
double APS5_VABI lgamma_nid_postfix(double x) { return std::lgamma(x); }
double APS5_VABI lgamma_nid_postfix(double x) {
signgam_nid_postfix = GammaSign(x);
return std::lgamma(x);
}

float APS5_VABI sinf_nid_postfix(float x) { return std::sin(x); }
float APS5_VABI cosf_nid_postfix(float x) { return std::cos(x); }
Expand Down
18 changes: 18 additions & 0 deletions core/libs/tests/GuestMath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ double APS5_VABI expm1_nid_postfix(double);
float APS5_VABI expm1f_nid_postfix(float);
float APS5_VABI erff_nid_postfix(float);
double APS5_VABI lgamma_nid_postfix(double);
extern int signgam_nid_postfix;
float APS5_VABI logbf_nid_postfix(float);
double APS5_VABI exp2_nid_postfix(double);
float APS5_VABI __powisf2_nid_postfix(float, int);
Expand Down Expand Up @@ -758,6 +759,22 @@ static void CheckNoConversionIsPositiveZero() {
Require(parsedWideLong == 0.L && !std::signbit(parsedWideLong) && wideEnd == wide);
}

static void CheckLgammaSign() {
Require(std::abs(lgamma_nid_postfix(-0.5) - 1.265512123484645) < 1e-15 && signgam_nid_postfix == -1);
Require(std::abs(lgamma_nid_postfix(-1.5) - 0.8600470153764812) < 1e-15 && signgam_nid_postfix == 1);
Require(std::abs(lgamma_nid_postfix(-2.5) + 0.056243716497674) < 1e-15 && signgam_nid_postfix == -1);
Require(std::abs(lgamma_nid_postfix(1.5) + 0.1207822376352454) < 1e-15 && signgam_nid_postfix == 1);
Require(std::abs(lgamma_nid_postfix(2.5) - 0.2846828704729192) < 1e-15 && signgam_nid_postfix == 1);
Require(std::abs(lgamma_nid_postfix(-1e-100) - 230.25850929940458) < 1e-12 && signgam_nid_postfix == -1);
Require(std::isinf(lgamma_nid_postfix(0.)) && signgam_nid_postfix == 1);
Require(std::isinf(lgamma_nid_postfix(-0.)) && signgam_nid_postfix == -1);
Require(std::isinf(lgamma_nid_postfix(-1.)) && signgam_nid_postfix == 1);
Require(std::isinf(lgamma_nid_postfix(-2.)) && signgam_nid_postfix == 1);
Require(std::isinf(lgamma_nid_postfix(std::numeric_limits<double>::infinity())) && signgam_nid_postfix == 1);
Require(std::isinf(lgamma_nid_postfix(-std::numeric_limits<double>::infinity())) && signgam_nid_postfix == 1);
Require(std::isnan(lgamma_nid_postfix(std::numeric_limits<double>::quiet_NaN())) && signgam_nid_postfix == 1);
}

int main() {
CheckFloatClassification();
CheckNoConversionIsPositiveZero();
Expand Down Expand Up @@ -837,6 +854,7 @@ int main() {
Require(lgamma_nid_postfix(1.) == 0. && lgamma_nid_postfix(2.) == 0. && std::isinf(lgamma_nid_postfix(0.)));
Require(std::abs(lgamma_nid_postfix(0.5) - 0.5723649429247001) < 1e-15 && std::abs(lgamma_nid_postfix(10.) - 12.801827480081469) < 1e-13);
Require(std::abs(lgamma_nid_postfix(-0.5) - 1.2655121234846454) < 1e-15);
CheckLgammaSign();
Require(llroundl_nid_postfix(2.5L) == 3 && llroundl_nid_postfix(-2.5L) == -3 && llroundl_nid_postfix(0.49999999999999999L) == 0);
Require(llroundl_nid_postfix(9007199254740993.0L) == 9007199254740993LL && llroundl_nid_postfix(-1e30L) == INT64_MIN);
Require(logbf_nid_postfix(8.f) == 3.f && logbf_nid_postfix(-0.75f) == -1.f);
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libc.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
- [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.
- [lgamma](../../../core/libs/prx/libc/src/Math.cpp) (libc) returns the host's `lgamma` but does not store the sign of Γ(x) in the guest's `signgam`, as FreeBSD 11's [`e_lgamma.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/e_lgamma.c) does through `__ieee754_lgamma_r`, because no guest `signgam` is exported. PPSA99004 imports it. Console behaviour is unverified
- [lgamma](../../../core/libs/prx/libc/src/Math.cpp) (libc) takes |Γ(x)| from the host's `lgamma`, so it can differ from the console's FreeBSD msun code in the last bit, and fills the guest `signgam` that FreeBSD 11's [`e_lgamma.c`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/e_lgamma.c) passes to [`__ieee754_lgamma_r`](https://github.com/freebsd/freebsd-src/blob/stable/11/lib/msun/src/e_lgamma_r.c) and that file's own rule derives from the argument: +1 at a pole, at the infinities and for NaN, -1 for a negative zero and otherwise, for x < 0, the sign of sin(pi*x), which is negative exactly where ceil(x) is even. `signgam` is a plain global here as it is on FreeBSD, so concurrent `lgamma` calls, and a guest reading it, race the same way. The magnitude on Windows comes from the CRT, which has no `lgamma_r`, so the sign rule is evaluated here instead of being taken from the host. `lgammaf` and `lgammal` are not exported here, so no other call fills `signgam`. The `libc.prx` bundled with PPSA59322 and PPSA76677 exports `signgam` (NID `5gOOC0kzW0c`) as a 4-byte global, so the console does too, but neither of those two titles imports it and no title available here does; console behaviour is unverified
- [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
- [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
- [atanhf](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows the `atanhf` of the `libc.prx` bundled with PPSA28319 (disassembly): FreeBSD msun's [`e_atanhf.c`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/e_atanhf.c) calling msun's [`log1pf`](https://github.com/freebsd/freebsd-src/blob/59bc2f095289a10b8cfa55cbb2b45c3bd6c2d4ae/lib/msun/src/s_log1pf.c), whose paths for the positive arguments it passes are inlined here; the exported `log1pf` is still the host's. 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)`, and for |x| = 1 it raises `FE_DIVBYZERO` and sets `errno` to `ERANGE` before returning `x / 0`; msun itself sets no `errno`. That code, run on a Windows host, gives the same bits, `errno` and floating-point exceptions as this implementation for all 2^32 arguments; it was not run on Linux. That the system `libSceLibcInternal`, which Dusklight (PPSA99305) imports while bundling a zero-return `libc.prx`, has the same code is assumed
Expand Down
Loading