From a568c94f224f42e6def046f277e21bd78b308128 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 3 May 2025 07:41:31 +0300 Subject: [PATCH 1/4] gh-133304: workaround for RISC-V in PyFloat_Pack4() --- Lib/test/test_capi/test_float.py | 6 +++--- ...-05-03-07-41-21.gh-issue-133304.YMuSne.rst | 1 + Objects/floatobject.c | 19 ++++++++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst diff --git a/Lib/test/test_capi/test_float.py b/Lib/test/test_capi/test_float.py index c832207fef0c1a..f7efe0d02549a3 100644 --- a/Lib/test/test_capi/test_float.py +++ b/Lib/test/test_capi/test_float.py @@ -199,13 +199,13 @@ def test_pack_unpack_roundtrip_for_nans(self): signaling = 0 quiet = int(not signaling) if size == 8: - payload = random.randint(signaling, 1 << 50) + payload = random.randint(signaling, 0x7ffffffffffff) i = (sign << 63) + (0x7ff << 52) + (quiet << 51) + payload elif size == 4: - payload = random.randint(signaling, 1 << 21) + payload = random.randint(signaling, 0x3fffff) i = (sign << 31) + (0xff << 23) + (quiet << 22) + payload elif size == 2: - payload = random.randint(signaling, 1 << 8) + payload = random.randint(signaling, 0x1ff) i = (sign << 15) + (0x1f << 10) + (quiet << 9) + payload data = bytes.fromhex(f'{i:x}') for endian in (BIG_ENDIAN, LITTLE_ENDIAN): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst new file mode 100644 index 00000000000000..f5bbc8bf2877b2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst @@ -0,0 +1 @@ +Workaround NaN's "canonicalization" in :c:func:`PyFloat_Pack4` on RISC-V. diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 692b6aa2cabd5e..2177f165e9b842 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2194,14 +2194,27 @@ PyFloat_Pack4(double x, char *data, int le) /* correct y if x was a sNaN, transformed to qNaN by conversion */ if (isnan(x)) { uint64_t v; + uint32_t u32; memcpy(&v, &x, 8); + memcpy(&u32, &y, 4); + if ((v & (1ULL << 51)) == 0) { - uint32_t u32; - memcpy(&u32, &y, 4); u32 &= ~(1 << 22); /* make sNaN */ - memcpy(&y, &u32, 4); } + + /* Workaround RISC-V: "If a NaN value is converted to a + * different floating-point type, the result is the + * canonical NaN of the new type". The canonical NaN here + * is a positive qNaN with zero payload. */ + if (v & (1ULL << 63)) { + u32 |= (1 << 31); /* set sign */ + } + /* add payload */ + u32 -= (u32 & 0x3fffff); + u32 += (uint32_t)((v & 0x7ffffffffffffULL) >> 29); + + memcpy(&y, &u32, 4); } unsigned char s[sizeof(float)]; From 6fdf4a6236a9fc45afcc375210649ea9254dd66d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 3 May 2025 09:05:39 +0300 Subject: [PATCH 2/4] +1 --- Objects/floatobject.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 2177f165e9b842..aaac4677b7e049 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2503,17 +2503,27 @@ PyFloat_Unpack4(const char *data, int le) /* return sNaN double if x was sNaN float */ if (isnan(x)) { + double y = x; /* will make qNaN double */ uint32_t v; + uint64_t u64; + memcpy(&v, &x, 4); + memcpy(&u64, &y, 8); if ((v & (1 << 22)) == 0) { - double y = x; /* will make qNaN double */ - uint64_t u64; - memcpy(&u64, &y, 8); u64 &= ~(1ULL << 51); /* make sNaN */ - memcpy(&y, &u64, 8); - return y; } + + /* Workaround RISC-V, see PyFloat_Pack4() */ + if (v & (1 << 31)) { + u64 |= (1ULL << 63); /* set sign */ + } + /* add payload */ + u64 -= (u64 & 0x7ffffffffffffULL); + u64 += ((v & 0x3fffffULL) << 29); + + memcpy(&y, &u64, 8); + return y; } return x; From 2599b27256c4a80505b144425cbf5126f6cc9cea Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 3 May 2025 10:10:48 +0300 Subject: [PATCH 3/4] Update Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst --- .../2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst index f5bbc8bf2877b2..64ae40a79a4d9c 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-03-07-41-21.gh-issue-133304.YMuSne.rst @@ -1 +1,2 @@ -Workaround NaN's "canonicalization" in :c:func:`PyFloat_Pack4` on RISC-V. +Workaround NaN's "canonicalization" in :c:func:`PyFloat_Pack4` +and :c:func:`PyFloat_Unpack4` on RISC-V. From 48a1041d260f32c03c02c526038d92a0b7269d22 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 3 May 2025 13:46:36 +0300 Subject: [PATCH 4/4] address review: make wa risc-v-specific --- Objects/floatobject.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Objects/floatobject.c b/Objects/floatobject.c index aaac4677b7e049..93e1973d6b32fc 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -2194,15 +2194,22 @@ PyFloat_Pack4(double x, char *data, int le) /* correct y if x was a sNaN, transformed to qNaN by conversion */ if (isnan(x)) { uint64_t v; - uint32_t u32; memcpy(&v, &x, 8); - memcpy(&u32, &y, 4); - +#ifndef __riscv if ((v & (1ULL << 51)) == 0) { + uint32_t u32; + memcpy(&u32, &y, 4); u32 &= ~(1 << 22); /* make sNaN */ + memcpy(&y, &u32, 4); } +#else + uint32_t u32; + memcpy(&u32, &y, 4); + if ((v & (1ULL << 51)) == 0) { + u32 &= ~(1 << 22); + } /* Workaround RISC-V: "If a NaN value is converted to a * different floating-point type, the result is the * canonical NaN of the new type". The canonical NaN here @@ -2215,6 +2222,7 @@ PyFloat_Pack4(double x, char *data, int le) u32 += (uint32_t)((v & 0x7ffffffffffffULL) >> 29); memcpy(&y, &u32, 4); +#endif } unsigned char s[sizeof(float)]; @@ -2503,17 +2511,26 @@ PyFloat_Unpack4(const char *data, int le) /* return sNaN double if x was sNaN float */ if (isnan(x)) { - double y = x; /* will make qNaN double */ uint32_t v; - uint64_t u64; - memcpy(&v, &x, 4); - memcpy(&u64, &y, 8); +#ifndef __riscv if ((v & (1 << 22)) == 0) { + double y = x; /* will make qNaN double */ + uint64_t u64; + memcpy(&u64, &y, 8); u64 &= ~(1ULL << 51); /* make sNaN */ + memcpy(&y, &u64, 8); + return y; } +#else + double y = x; + uint64_t u64; + memcpy(&u64, &y, 8); + if ((v & (1 << 22)) == 0) { + u64 &= ~(1ULL << 51); + } /* Workaround RISC-V, see PyFloat_Pack4() */ if (v & (1 << 31)) { u64 |= (1ULL << 63); /* set sign */ @@ -2524,6 +2541,7 @@ PyFloat_Unpack4(const char *data, int le) memcpy(&y, &u64, 8); return y; +#endif } return x;