fix(libc): scale hexadecimal values using the guest x87 mode

This commit is contained in:
JJenkx committed 2026-10-29 08:52:29 -04:00
1 parent 04fe64c2f6
commit 01eef3d3e6
3 files changed
+71 -33

No files matched your search

+54 -21
View File
@@ -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));
}
+16 -11
View File
@@ -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) {
+1 -1
View File
@@ -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.