Fix CI: probe for subnormal-flushing libc instead of assuming it

The power-of-two canonicalization test's remaining failure (Windows
Intel oneAPI, MSYS2 clangarm64) wasn't the ldexp() test-reference bug
fixed previously -- it's the library's own H5Z__rewrite_hexfloats(),
whose strtod()/snprintf("%.16e") calls are corrupted by ambient
flush-to-zero (FTZ) mode on those platforms: 0x1p-1074 round-tripped
to a literal 0.0 ("rate = 0.0000000000000000e+00"), not merely a
miscompared nonzero value. FTZ/DAZ are a per-thread hardware register
(MXCSR on x86, FPCR on AArch64) that some toolchains (Intel icc/icx
via -fp-model=fast, NVHPC, some Clang-on-AArch64 configs) enable
process-wide by default, corrupting subnormal results inside the
platform's own libc, not just in code HDF5 itself compiled.

Considered adding an SSE-intrinsics/AArch64-inline-asm guard in
H5Zconfig.c to force IEEE-correct rounding around those calls, but
that puts architecture-specific register manipulation in a core
parsing path for a case that only matters for filter parameters that
are exact subnormal doubles (magnitude < ~2.2e-308) -- not a realistic
compression level, tolerance, or scale factor. Instead, added a
runtime probe: round-trip a known subnormal through the same two libc
calls H5Z__rewrite_hexfloats() itself uses, and skip only the two
true-subnormal exponents (2^-1074, 2^-1073) if that probe shows this
platform's libc doesn't preserve them -- no arch-specific code, no
assumptions about which toolchains are affected, and full coverage
preserved everywhere the underlying libc actually behaves (confirmed
locally: GCC/glibc's strtod/printf already round-trip 2^-1074
correctly, even under artificially forced FTZ+DAZ, so this platform's
probe would find subnormals_ok and run the test unchanged).
This commit is contained in:
Scot Breitenfeld
2026-09-04 13:06:43 -05:00
parent e251613b32
commit 2761464f9e
+28 -1
View File
@@ -3697,7 +3697,17 @@ test_config_canonicalization(hid_t fapl)
* boundary -- and exact powers of two are both where such a boundary sits
* and what a user writes in hex in the first place. Appending the hex
* form and appending its canonical decimal must pack the identical
* double. (RFC-HDFG-2026-001 fmt-01c) --- */
* double. (RFC-HDFG-2026-001 fmt-01c)
*
* True subnormal exponents (below -1022) are probed, not assumed: some
* toolchains (Intel icc/icx via -fp-model=fast -- the default at -O2 and
* above -- NVHPC, and some Clang-on-AArch64 configurations) enable
* flush-to-zero mode process-wide by default, which silently corrupts
* subnormal results inside strtod()/printf() *in the platform's own
* libc*, not just in code this file compiled. Rather than guessing which
* toolchains that affects, round-trip a known subnormal through the same
* two calls H5Z__rewrite_hexfloats() itself uses and skip only the
* exponents that probe demonstrates are unsafe to assert on here. --- */
TESTING("canonicalization: hex rewrite is value-transparent at powers of two");
{
/* smallest subnormal and smallest normal at the bottom, the 2^-36
@@ -3706,6 +3716,20 @@ test_config_canonicalization(hid_t fapl)
static const int exps[] = {-1074, -1073, -1022, -1021, -100, -37, -36, -35,
-1, 0, 1, 52, 53, 100, 512, 1023};
size_t i;
bool subnormals_ok;
{
char probe[32];
double smallest_subnormal = pow2_exact(-1074);
double roundtrip;
snprintf(probe, sizeof(probe), "%.16e", smallest_subnormal);
roundtrip = strtod(probe, NULL);
subnormals_ok = (memcmp(&roundtrip, &smallest_subnormal, sizeof(roundtrip)) == 0);
if (!subnormals_ok)
puts(" (skipping true-subnormal exponents: this platform's strtod()/printf() "
"flush them to zero)");
}
for (i = 0; i < sizeof(exps) / sizeof(exps[0]); i++) {
char hexstr[64];
@@ -3714,6 +3738,9 @@ test_config_canonicalization(hid_t fapl)
double from_hex = 0.0;
double from_dec = 0.0;
if (!subnormals_ok && exps[i] < -1022)
continue;
snprintf(hexstr, sizeof(hexstr), "rate = 0x1p%+d", exps[i]);
if ((dcpl = canon_make_dcpl(hexstr)) < 0)