mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
fix(libc): scale hexadecimal values using the guest x87 mode
This commit is contained in:
1 parent
04fe64c2f6
commit
01eef3d3e6
3 files changed
+71
-33
No files matched your search
@@ -1,16 +1,17 @@
|
||||
#include "prx/libc/include/Api.hpp"
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <cfenv>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <xmmintrin.h>
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
@@ -33,6 +34,57 @@ int HexadecimalDigit(char value) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
template<typename TFloat>
|
||||
TFloat ScaleHexadecimal(TFloat value, std::int64_t power) {
|
||||
constexpr unsigned Precision = std::numeric_limits<TFloat>::digits;
|
||||
static_assert(Precision == 24 || Precision == 53 || Precision == 64);
|
||||
constexpr auto Hidden = std::uint64_t{1} << (Precision - 1);
|
||||
constexpr int MaximumExponent = 2 * std::numeric_limits<TFloat>::max_exponent - 1;
|
||||
std::uint64_t mantissa;
|
||||
int exponent;
|
||||
if constexpr (sizeof(TFloat) == 4) {
|
||||
const auto bits = std::bit_cast<std::uint32_t>(value);
|
||||
mantissa = (bits & (Hidden - 1)) | Hidden;
|
||||
exponent = (bits >> (Precision - 1)) & MaximumExponent;
|
||||
} else if constexpr (sizeof(TFloat) == 8) {
|
||||
const auto bits = std::bit_cast<std::uint64_t>(value);
|
||||
mantissa = (bits & (Hidden - 1)) | Hidden;
|
||||
exponent = (bits >> (Precision - 1)) & MaximumExponent;
|
||||
} else {
|
||||
static_assert(Precision == 64 && sizeof(TFloat) >= 10 && std::endian::native == std::endian::little);
|
||||
std::uint16_t encoded;
|
||||
std::memcpy(&mantissa, &value, sizeof(mantissa));
|
||||
std::memcpy(&encoded, reinterpret_cast<const unsigned char*>(&value) + 8, sizeof(encoded));
|
||||
exponent = encoded & MaximumExponent;
|
||||
}
|
||||
auto adjusted = exponent + power;
|
||||
if (adjusted >= MaximumExponent) return std::numeric_limits<TFloat>::infinity();
|
||||
if (adjusted <= 0) {
|
||||
const auto shift = 1 - adjusted;
|
||||
if (shift > Precision) return static_cast<TFloat>(0);
|
||||
const auto remainder = shift == 64 ? mantissa : mantissa & ((std::uint64_t{1} << shift) - 1);
|
||||
mantissa = shift == 64 ? 0 : mantissa >> shift;
|
||||
if (remainder != 0) {
|
||||
GuestFloatingPointEnvironment environment;
|
||||
if (fegetenv_nid_postfix(&environment) != 0) throw std::runtime_error("cannot read hexadecimal binary rounding");
|
||||
const auto rounding = (environment.control >> 10u) & 3u;
|
||||
const auto half = std::uint64_t{1} << (shift - 1);
|
||||
if (rounding == 2u || (rounding == 0u && (remainder > half || (remainder == half && (mantissa & 1u) != 0)))) ++mantissa;
|
||||
}
|
||||
adjusted = (mantissa & Hidden) != 0 ? 1 : 0;
|
||||
}
|
||||
if constexpr (sizeof(TFloat) == 4) {
|
||||
return std::bit_cast<TFloat>(static_cast<std::uint32_t>((static_cast<std::uint64_t>(adjusted) << (Precision - 1)) | (mantissa & (Hidden - 1))));
|
||||
} else if constexpr (sizeof(TFloat) == 8) {
|
||||
return std::bit_cast<TFloat>((static_cast<std::uint64_t>(adjusted) << (Precision - 1)) | (mantissa & (Hidden - 1)));
|
||||
} else {
|
||||
const auto encoded = static_cast<std::uint16_t>(adjusted);
|
||||
std::memcpy(&value, &mantissa, sizeof(mantissa));
|
||||
std::memcpy(reinterpret_cast<unsigned char*>(&value) + 8, &encoded, sizeof(encoded));
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TFloat>
|
||||
std::optional<TFloat> UnscaledHexadecimal(const char* text, char** end) {
|
||||
const char* cursor = text;
|
||||
@@ -109,26 +161,7 @@ std::optional<TFloat> UnscaledHexadecimal(const char* text, char** end) {
|
||||
TFloat value;
|
||||
if constexpr (std::numeric_limits<TFloat>::digits <= 53) value = static_cast<TFloat>(low);
|
||||
else value = std::ldexp(static_cast<TFloat>(high), 64) + static_cast<TFloat>(low);
|
||||
if (exponent != 0) {
|
||||
const int rounding = std::fegetround();
|
||||
const int savedErrno = errno;
|
||||
std::fenv_t saved;
|
||||
if (std::feholdexcept(&saved) != 0) throw std::runtime_error("cannot hold hexadecimal floating environment");
|
||||
if (std::fesetround(rounding) != 0) {
|
||||
std::fesetenv(&saved);
|
||||
errno = savedErrno;
|
||||
throw std::runtime_error("cannot select hexadecimal binary rounding");
|
||||
}
|
||||
_mm_setcsr(_mm_getcsr() & ~0x8040u);
|
||||
int binary = 0;
|
||||
std::frexp(value, &binary);
|
||||
const auto combined = exponent + binary;
|
||||
if (combined > std::numeric_limits<TFloat>::max_exponent) value = std::numeric_limits<TFloat>::infinity();
|
||||
else if (combined < std::numeric_limits<TFloat>::min_exponent - std::numeric_limits<TFloat>::digits) value = 0;
|
||||
else value = std::scalbn(value, static_cast<int>(exponent));
|
||||
if (std::fesetenv(&saved) != 0) throw std::runtime_error("cannot restore hexadecimal floating environment");
|
||||
errno = savedErrno;
|
||||
}
|
||||
if (exponent != 0) value = ScaleHexadecimal(value, exponent);
|
||||
return std::copysign(value, negative ? static_cast<TFloat>(-1) : static_cast<TFloat>(1));
|
||||
}
|
||||
|
||||
|
||||
@@ -72,33 +72,37 @@ void CheckUnscaledHexadecimal(TFloat (APS5_VABI *parse)(const char*, char**, std
|
||||
std::feclearexcept(FE_ALL_EXCEPT);
|
||||
std::feraiseexcept(FE_DIVBYZERO);
|
||||
const unsigned control = _mm_getcsr() & ~0x3fu;
|
||||
const int rounding = std::fegetround();
|
||||
unsigned short rounding;
|
||||
asm volatile("fnstcw %0" : "=m"(rounding));
|
||||
int error = 123;
|
||||
char* end = nullptr;
|
||||
const auto value = parse(text, &end, 0, &error);
|
||||
const int flags = std::fetestexcept(FloatingExceptions);
|
||||
unsigned short after;
|
||||
asm volatile("fnstcw %0" : "=m"(after));
|
||||
constexpr std::size_t Bytes = std::numeric_limits<TFloat>::digits == 64 ? 10 : sizeof(TFloat);
|
||||
Require(std::memcmp(&value, &expected, Bytes) == 0, "unscaled hexadecimal value bits");
|
||||
Require(end == text + std::strlen(text) - 4 && error == 0 && *__error_nid_postfix() == 7, "unscaled hexadecimal end and errors");
|
||||
Require(flags == (FE_DIVBYZERO | expectedFlags), "unscaled hexadecimal floating flags");
|
||||
Require((_mm_getcsr() & ~0x3fu) == control && std::fegetround() == rounding, "unscaled hexadecimal preserves floating controls");
|
||||
Require((_mm_getcsr() & ~0x3fu) == control && after == rounding, "unscaled hexadecimal preserves floating controls");
|
||||
}
|
||||
|
||||
void TestUnscaledHexadecimal() {
|
||||
std::fenv_t saved;
|
||||
Require(std::fegetenv(&saved) == 0, "save unscaled hexadecimal test environment");
|
||||
GuestFloatingPointEnvironment saved;
|
||||
Require(fegetenv_nid_postfix(&saved) == 0, "save unscaled hexadecimal test environment");
|
||||
for (const unsigned flushing : {0u, 0x40u, 0x8000u, 0x8040u}) {
|
||||
for (const int rounding : {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}) {
|
||||
for (unsigned rounding = 0; rounding < 4u; ++rounding) {
|
||||
for (unsigned sse = 0; sse < 4u; ++sse) {
|
||||
Require(std::fesetenv(FE_DFL_ENV) == 0 && std::fesetround(rounding) == 0, "set unscaled hexadecimal rounding");
|
||||
_mm_setcsr((_mm_getcsr() & ~0x6000u) | (sse << 13u) | flushing);
|
||||
const auto x87 = static_cast<unsigned short>(0x37fu | (rounding << 10u));
|
||||
asm volatile("fnclex\n\tfldcw %0" : : "m"(x87));
|
||||
_mm_setcsr(0x1f80u | (sse << 13u) | flushing);
|
||||
CheckUnscaledHexadecimal(_Stofx_nid_postfix, "0x1.0000011p0tail", sse == 2u ? 0x1.000002p0f : 1.0f, FE_INEXACT);
|
||||
CheckUnscaledHexadecimal(_Stofx_nid_postfix, "0x1.0000018p0tail", 0x1.000002p0f, 0);
|
||||
CheckUnscaledHexadecimal(_Stodx_nid_postfix, "0x1.00000000000008p0tail", 0x1.0000000000001p0, 0);
|
||||
CheckUnscaledHexadecimal(_Stodx_nid_postfix, "0x1.00000000000007p0tail", 1.0, 0);
|
||||
CheckUnscaledHexadecimal(_Stoldx_nid_postfix, "0x1.00000000000000011p0tail", rounding == FE_UPWARD || rounding == FE_TONEAREST ? 0x1.0000000000000002p0L : 1.0L, FE_INEXACT);
|
||||
CheckUnscaledHexadecimal(_Stoldx_nid_postfix, "0x1.000000000000000100001p0tail", rounding == FE_UPWARD ? 0x1.0000000000000002p0L : 1.0L, FE_INEXACT);
|
||||
const bool up = rounding == FE_UPWARD;
|
||||
CheckUnscaledHexadecimal(_Stoldx_nid_postfix, "0x1.00000000000000011p0tail", rounding == 2u || rounding == 0u ? 0x1.0000000000000002p0L : 1.0L, FE_INEXACT);
|
||||
CheckUnscaledHexadecimal(_Stoldx_nid_postfix, "0x1.000000000000000100001p0tail", rounding == 2u ? 0x1.0000000000000002p0L : 1.0L, FE_INEXACT);
|
||||
const bool up = rounding == 2u;
|
||||
CheckUnscaledHexadecimal(_Stofx_nid_postfix, "-0x1p-150tail", up ? -std::numeric_limits<float>::denorm_min() : -0.0f, 0);
|
||||
CheckUnscaledHexadecimal(_Stodx_nid_postfix, "-0x1p-1075tail", up ? -std::numeric_limits<double>::denorm_min() : -0.0, 0);
|
||||
CheckUnscaledHexadecimal(_Stoldx_nid_postfix, "-0x1p-16446tail", up ? -std::numeric_limits<long double>::denorm_min() : -0.0L, 0);
|
||||
@@ -111,7 +115,8 @@ void TestUnscaledHexadecimal() {
|
||||
}
|
||||
}
|
||||
}
|
||||
Require(std::fesetenv(&saved) == 0, "restore unscaled hexadecimal test environment");
|
||||
asm volatile("fldenv %0" : : "m"(saved) : "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)");
|
||||
_mm_setcsr(saved.mxcsr);
|
||||
}
|
||||
|
||||
void CheckExp(double x, double y, short offset, double expected, short expectedKind, int expectedErrno, int expectedFlags, const char* message) {
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
- [std::error_category, std::future_category and std::future_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) - measured from PPSA02530's `libc.prx`, except that the future category's deleting destructor throws instead of freeing it, the string destructor frees the block instead of the console header path, and a `_Doraise` copy of a heap message and header size are unverified. `error_code` keeps the value in the second dword, so `default_error_condition` shifts it into the high half and `equivalent` compares that dword and the category. `_Future_error_category` derives from `_Generic_error_category`, is `future` and maps 1..4 to `broken promise`, `future already retrieved`, `promise already satisfied` and `no state`; other values use the generic map, but `future_error::what()` returns null outside 1..4. `future_error` is 0x40 bytes (vtable, `GuestLocale::String` at 0x8, `error_code` at 0x30). `_Rethrow_future_exception` delegates to `rethrow_exception`.
|
||||
- [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc) follows Dinkumware `thread0.cpp` ([VC12](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/thread0.cpp), [STL](https://github.com/microsoft/STL/blob/da52dc0f/stl/src/thread0.cpp)), without console verification. Codes outside 0..6 throw `std::out_of_range`, the deleting destructor throws, and STREAMS errnos absent from FreeBSD 11.4 [`errno.h`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h) (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are omitted. Codes 0..6 throw a 32-byte [VC12 `system_error`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/system_error) with FreeBSD errnos (16, 22, 3, 12, 1, 11, 35), a static `generic_category` (not `_Generic_object`), and unverified 40-byte [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) messages from [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126/src/syserror.cpp)
|
||||
- [Call tracing](../../../core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp) (`--trace-imports`, `APS5_TRACE_CALLS`) records only the entry of a call, not its arguments or result. One process-wide ring keeps the last 65536 calls, so a busy thread pushes the others out; the reporting thread's own calls are listed only while they are still in that ring. Calls are counted per traced target; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; wider vector arguments survive only while the recording code avoids AVX, which is untested. Linux and macOS have no crash report, so they print the trace only on `SIGABRT` (guest or host `abort()`), not on `SIGSEGV`; a caller in a module loaded after the last traced import prints without a module name, and a thread aborting while another writes the report waits for it at most 2 seconds. An arm64 libc build omits the recorder
|
||||
- [_Stodx, _Stofx and _Stoldx unscaled hexadecimal conversion](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) is unverified with unmasked floating-point exceptions, nondefault x87 precision control, or non-C locale prefix classification.
|
||||
- [_Stodx, _Stofx and _Stoldx unscaled hexadecimal conversion](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) differs from the bundled SDK modules for `_Stoldx("0x123456789abcdef123456p0", ..., 0, ...)` with 24-bit x87 precision and rounding upward. Other nondefault precision modes, unmasked exception delivery and non-C locale prefix classification remain incompletely characterized.
|
||||
- [_Stopfx and _WStopfx](../../../core/libs/prx/libc/src/FloatingPrefix.cpp) follow the prefix results and pointer updates of Dinkumware V6.00 [xstopfx.c](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/xstopfx.c) and [xwstopfx.c](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/xwstopfx.c), without console verification. Sony signatures, module/library versions and locale equivalence are unverified; the wide code units follow the existing 16-bit libc string ABI, and character classification inherits the guest ctype/rune limitations. The guest default rune table rejects non-ASCII NaN payload letters that native Windows `iswalnum` accepts, including U+00AA in its C locale; console classification is unmeasured. A null cursor or input pointer throws; console invalid-pointer behavior is unknown. These helpers classify prefixes and do not convert numbers or consume their digits.
|
||||
- [_Dtest](../../../core/libs/prx/libc/src/Math.cpp) (libc) follows Dinkumware double classification matching _FDtest: finite values return -1, subnormals -2, zeros 0, infinities 1, and NaNs 2.
|
||||
- [_Exp and _FExp](../../../core/libs/prx/libc/src/DinkumwareInternals.cpp) (libc) - signaling NaNs, directed rounding and flush-to-zero modes are unverified, and titles whose `libc.prx` contracts multiply-adds to FMA, such as PPSA13934, differ by one unit in the last place on 889 `_FExp` and 15 `_Exp` of 300,000 random calls. Finite results use the constants, evaluation order and half-epsilon shortcut of the `libc.prx` that titles ship and match that of PPSA04158, PPSA05916, PPSA07923 and PPSA20423 on those calls, run on a Windows host. Special operands follow PRAGMATA PPSA02530's bundled `libc.prx`: the x-only cutoffs at ±229/±1842 store `0.0*y` below and `y*inf` with overflow above, ignoring the offset; a zero multiplier keeps its sign, a NaN multiplier leaves `*px` unchanged, and `+inf` with zero or `-inf` with infinity is invalid.
|
||||
|
||||
Reference in new issue
Block a user