feat(libc): implement asinl
RetroArch 1.0.0-beta.1 (PPSA99169) stops on main 0f863c10 at
FAIL: unresolved ELF import Nx-F5v0-qU8 from libSceLibcInternal.prx
Nx-F5v0-qU8 is asinl (tools/nid_names.py, aerolib).
The implementation follows the asinl of the libc.prx bundled with
PPSA28319, read from its disassembly: FreeBSD msun's e_asinl.c with
the ld80 invtrig.c coefficients (9f043204), which the binary holds as
the same 80-bit constants, computed in the x87 long double the guest
uses. +-1 returns +-pi/2 rounded to 64 bits and +-0 keeps its sign.
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 sets EDOM too.
20 million random arguments in (-1, 1) plus the branch edges give the
same bits as that code from the title's libc.prx run on a Windows
host. guest_math pins a set of those bits and checks the domain
errors' errno and FE_INVALID, and that errno is untouched otherwise.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
2c6e3f53ff
commit
5f3fb3dddd
|
|
@ -7,6 +7,7 @@
|
|||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
|
@ -78,6 +79,52 @@ float SinhfExp(float x) {
|
|||
return y * std::bit_cast<float>(static_cast<std::uint32_t>(0x7f + k) << 23);
|
||||
}
|
||||
|
||||
static_assert(std::numeric_limits<long double>::digits == 64, "guest long double is the x87 80-bit format");
|
||||
|
||||
struct ExtendedParts {
|
||||
std::uint64_t mantissa;
|
||||
std::uint16_t signExponent;
|
||||
};
|
||||
|
||||
ExtendedParts SplitExtended(long double value) {
|
||||
unsigned char bytes[sizeof(long double)];
|
||||
std::memcpy(bytes, &value, sizeof(value));
|
||||
ExtendedParts parts;
|
||||
std::memcpy(&parts.mantissa, bytes, sizeof(parts.mantissa));
|
||||
std::memcpy(&parts.signExponent, bytes + sizeof(parts.mantissa), sizeof(parts.signExponent));
|
||||
return parts;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 long double AsinDenominatorCoefficients[] = {
|
||||
-2.94788392796209867269e+00L, 3.27309890266528636716e+00L, -1.68285799854822427013e+00L, 3.90699412641738801874e-01L,
|
||||
-3.14365703596053263322e-02L,
|
||||
};
|
||||
constexpr long double HalfPiHigh = 1.57079632679489661926e+00L;
|
||||
constexpr long double HalfPiLow = -2.50827880633416601173e-20L;
|
||||
constexpr long double QuarterPiHigh = 7.85398163397448309628e-01L;
|
||||
|
||||
long double AsinNumerator(long double 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) {
|
||||
const auto& q = AsinDenominatorCoefficients;
|
||||
return 1.0L + t * (q[0] + t * (q[1] + t * (q[2] + t * (q[3] + t * q[4]))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
@ -147,6 +194,36 @@ double APS5_VABI trunc_nid_postfix(double x) { return std::trunc(x); }
|
|||
double APS5_VABI fma_nid_postfix(double x, double y, double z) { return std::fma(x, y, z); }
|
||||
long double APS5_VABI acosl_nid_postfix(long double x) { return std::acos(x); }
|
||||
|
||||
long double APS5_VABI asinl_nid_postfix(long double x) {
|
||||
const ExtendedParts parts = SplitExtended(x);
|
||||
const int exponent = parts.signExponent & 0x7fff;
|
||||
if (exponent >= 0x3fff) {
|
||||
if (exponent == 0x3fff && (parts.mantissa & 0x7fffffffffffffffull) == 0) return x * HalfPiHigh + x * HalfPiLow;
|
||||
std::feraiseexcept(FE_INVALID);
|
||||
errno = EDOM;
|
||||
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;
|
||||
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;
|
||||
if ((parts.mantissa >> 32) >= 0xf3333333u) {
|
||||
result = HalfPiHigh - (2.0L * (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));
|
||||
}
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ int APS5_VABI abs_nid_postfix(int);
|
|||
double APS5_VABI trunc_nid_postfix(double);
|
||||
double APS5_VABI fma_nid_postfix(double, double, double);
|
||||
long double APS5_VABI acosl_nid_postfix(long double);
|
||||
long double APS5_VABI asinl_nid_postfix(long double);
|
||||
long double APS5_VABI frexpl_nid_postfix(long double, int*);
|
||||
struct LibcFloatConstant { std::uint32_t bits[4]; };
|
||||
extern LibcFloatConstant _FInf_nid_postfix;
|
||||
|
|
@ -185,6 +186,49 @@ static void CheckFmodErrno() {
|
|||
Require(fmod_nid_postfix(1e300, 3.0) == std::fmod(1e300, 3.0));
|
||||
}
|
||||
|
||||
static long double MakeExtended(std::uint64_t mantissa, std::uint16_t signExponent) {
|
||||
unsigned char bytes[sizeof(long double)]{};
|
||||
std::memcpy(bytes, &mantissa, 8);
|
||||
std::memcpy(bytes + 8, &signExponent, 2);
|
||||
long double value = 0;
|
||||
std::memcpy(&value, bytes, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static void CheckAsinl() {
|
||||
const struct { std::uint64_t inputMantissa; std::uint16_t inputSignExponent; std::uint64_t outputMantissa; std::uint16_t outputSignExponent; } cases[] = {
|
||||
{0, 0x0000, 0, 0x0000}, {0, 0x8000, 0, 0x8000},
|
||||
{0x8000000000000000ull, 0x3fd7, 0x8000000000000000ull, 0x3fd7}, {0x8000000000000000ull, 0xbfd7, 0x8000000000000000ull, 0xbfd7},
|
||||
{0x8000000000000000ull, 0x3ffd, 0x815f4e705c3e68f3ull, 0x3ffd}, {0x8000000000000000ull, 0xbffd, 0x815f4e705c3e68f3ull, 0xbffd},
|
||||
{0xcccccccccccccccdull, 0x3ffb, 0xcd2493b01bc41e02ull, 0x3ffb},
|
||||
{0x8000000000000000ull, 0x3ffe, 0x860a91c16b9b2c23ull, 0x3ffe}, {0x8000000000000000ull, 0xbffe, 0x860a91c16b9b2c23ull, 0xbffe},
|
||||
{0xc000000000000000ull, 0xbffe, 0xd91a98ae3406e041ull, 0xbffe}, {0xe666666666666666ull, 0x3ffe, 0x8f549b82fd0b8fa3ull, 0x3fff},
|
||||
{0xf333333333333333ull, 0x3ffe, 0xa06a08acf9a04f78ull, 0x3fff}, {0xfd70a3d70a3d70a4ull, 0xbffe, 0xb6f1e3799ea8ecc3ull, 0xbfff},
|
||||
{0xffffffffffffffffull, 0x3ffe, 0xc90fdaa16c63cf01ull, 0x3fff},
|
||||
{0x8000000000000000ull, 0x3fff, 0xc90fdaa22168c235ull, 0x3fff}, {0x8000000000000000ull, 0xbfff, 0xc90fdaa22168c235ull, 0xbfff},
|
||||
};
|
||||
for (const auto& test : cases) {
|
||||
*__error_nid_postfix() = 12345;
|
||||
const long double result = asinl_nid_postfix(MakeExtended(test.inputMantissa, test.inputSignExponent));
|
||||
if (!ExtendedBits(result, test.outputMantissa, test.outputSignExponent) || *__error_nid_postfix() != 12345) {
|
||||
std::fprintf(stderr, "Guest asinl failed for %016llx:%04x\n", static_cast<unsigned long long>(test.inputMantissa), test.inputSignExponent);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
Require(std::fabs(asinl_nid_postfix(0.5L) - 0.523598775598298873077107230547L) <= 0x1p-64L);
|
||||
const long double domainErrors[] = {
|
||||
1.5L, -2.0L, MakeExtended(0x8000000000000001ull, 0x3fff), MakeExtended(0x8000000000000001ull, 0xbfff),
|
||||
std::numeric_limits<long double>::max(), std::numeric_limits<long double>::infinity(), -std::numeric_limits<long double>::infinity(),
|
||||
std::numeric_limits<long double>::quiet_NaN(),
|
||||
};
|
||||
for (const long double input : domainErrors) {
|
||||
std::feclearexcept(FE_ALL_EXCEPT);
|
||||
*__error_nid_postfix() = 0;
|
||||
Require(std::isnan(asinl_nid_postfix(input)) && *__error_nid_postfix() == EDOM && std::fetestexcept(FE_INVALID) != 0);
|
||||
}
|
||||
*__error_nid_postfix() = 0;
|
||||
}
|
||||
|
||||
static void CheckIntegerConversions() {
|
||||
for (const long long numerator : {4294967301LL, -4294967301LL}) {
|
||||
for (const long long denominator : {3LL, -3LL}) {
|
||||
|
|
@ -575,6 +619,7 @@ int main() {
|
|||
CheckSquareRoot();
|
||||
CheckExactOperations();
|
||||
CheckLongDouble();
|
||||
CheckAsinl();
|
||||
Require(scalbn_nid_postfix(0.75, -2) == 0.1875);
|
||||
Require(scalbnf_nid_postfix(0.75f, 4) == 12.f);
|
||||
Require(nextafterf_nid_postfix(1.f, 2.f) == 0x1.000002p0f && nextafterf_nid_postfix(1.f, 0.f) == 0x1.fffffep-1f);
|
||||
|
|
|
|||
|
|
@ -432,6 +432,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [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
|
||||
- [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
|
||||
- [SPIRV-Tools optimization](../../core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp) skips CCP and Simplification for modules with storage-only 8- or 16-bit types because the pinned folder produces illegal narrow constants. This also disables unrelated constant folding; the restricted full pipeline mirrors the pinned performance recipe.
|
||||
- [SRT signed comparison](../../core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp) evaluates `SLessThan32` as signed 32-bit less-than, matching the existing SPIR-V backend and [SPIR-V `OpSLessThan`](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpSLessThan). Signed boundary descriptor expressions are tested on the host; native RDNA2/PS5 execution and title behavior remain unverified.
|
||||
- On macOS the [write watch](../../core/libs/prx/libc/src/GuestWriteWatch.cpp) write-protects watched pages and records the first write to each from a SIGBUS/SIGSEGV handler, because macOS has no counterpart to `userfaultfd` write-protect or `MEM_WRITE_WATCH`. The protection is real, so:
|
||||
|
|
|
|||
Loading…
Reference in New Issue