diff --git a/core/libs/prx/libc/src/DinkumwareInternals.cpp b/core/libs/prx/libc/src/DinkumwareInternals.cpp index 8ff4cb1dbc..aa1a7c8430 100644 --- a/core/libs/prx/libc/src/DinkumwareInternals.cpp +++ b/core/libs/prx/libc/src/DinkumwareInternals.cpp @@ -1,16 +1,17 @@ #include "prx/libc/include/Api.hpp" #include +#include #include #include #include #include #include #include +#include #include #include #include #include -#include #include "prx/libc/include/General.hpp" @@ -33,6 +34,57 @@ int HexadecimalDigit(char value) { return -1; } +template +TFloat ScaleHexadecimal(TFloat value, std::int64_t power) { + constexpr unsigned Precision = std::numeric_limits::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::max_exponent - 1; + std::uint64_t mantissa; + int exponent; + if constexpr (sizeof(TFloat) == 4) { + const auto bits = std::bit_cast(value); + mantissa = (bits & (Hidden - 1)) | Hidden; + exponent = (bits >> (Precision - 1)) & MaximumExponent; + } else if constexpr (sizeof(TFloat) == 8) { + const auto bits = std::bit_cast(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(&value) + 8, sizeof(encoded)); + exponent = encoded & MaximumExponent; + } + auto adjusted = exponent + power; + if (adjusted >= MaximumExponent) return std::numeric_limits::infinity(); + if (adjusted <= 0) { + const auto shift = 1 - adjusted; + if (shift > Precision) return static_cast(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(static_cast((static_cast(adjusted) << (Precision - 1)) | (mantissa & (Hidden - 1)))); + } else if constexpr (sizeof(TFloat) == 8) { + return std::bit_cast((static_cast(adjusted) << (Precision - 1)) | (mantissa & (Hidden - 1))); + } else { + const auto encoded = static_cast(adjusted); + std::memcpy(&value, &mantissa, sizeof(mantissa)); + std::memcpy(reinterpret_cast(&value) + 8, &encoded, sizeof(encoded)); + return value; + } +} + template std::optional UnscaledHexadecimal(const char* text, char** end) { const char* cursor = text; @@ -109,26 +161,7 @@ std::optional UnscaledHexadecimal(const char* text, char** end) { TFloat value; if constexpr (std::numeric_limits::digits <= 53) value = static_cast(low); else value = std::ldexp(static_cast(high), 64) + static_cast(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::max_exponent) value = std::numeric_limits::infinity(); - else if (combined < std::numeric_limits::min_exponent - std::numeric_limits::digits) value = 0; - else value = std::scalbn(value, static_cast(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(-1) : static_cast(1)); } diff --git a/core/libs/tests/GuestDinkumwareInternals.cpp b/core/libs/tests/GuestDinkumwareInternals.cpp index d27c3af070..8c19183482 100644 --- a/core/libs/tests/GuestDinkumwareInternals.cpp +++ b/core/libs/tests/GuestDinkumwareInternals.cpp @@ -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::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(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::denorm_min() : -0.0f, 0); CheckUnscaledHexadecimal(_Stodx_nid_postfix, "-0x1p-1075tail", up ? -std::numeric_limits::denorm_min() : -0.0, 0); CheckUnscaledHexadecimal(_Stoldx_nid_postfix, "-0x1p-16446tail", up ? -std::numeric_limits::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) { diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index b4d6fc78d1..742bf47725 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -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.