CrossSIMD: Keep the C++ fallback's integer ops defined, like the SIMD ones

The fallback (which RISC-V uses) did its integer arithmetic in plain int,
where signed overflow and left shifts of negative values are undefined,
and converted out of range floats with a cast, which is undefined too:
RISC-V gives INT_MAX for NaN where SSE gives INT_MIN and NEON 0. With GCC
on RISC-V that broke GEUVSpanCore in the GEMath unit test. Now the
arithmetic wraps through uint32_t, like the SIMD instructions, and an out
of range or NaN conversion gives INT_MIN, like SSE.

Also skip GERowSum's zero terms before computing their shift, as their
exponent can be INT_MIN. Found with -fsanitize=undefined on the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-09 11:12:48 +02:00
1 parent 10f5de6ded
commit d9b36b7e46
2 files changed
+19 -8

No files matched your search

+14 -7
View File
@@ -1664,14 +1664,16 @@ struct Vec4S32 {
int operator[](size_t index) const { return v[index]; }
// Wrapping, like the SIMD versions: signed overflow would be undefined, and GCC and Clang don't agree on it.
static int32_t Wrap(uint32_t x) { return (int32_t)x; }
Vec4S32 operator +(Vec4S32 other) const {
return Vec4S32{ { v[0] + other.v[0], v[1] + other.v[1], v[2] + other.v[2], v[3] + other.v[3], } };
return Vec4S32{ { Wrap((uint32_t)v[0] + (uint32_t)other.v[0]), Wrap((uint32_t)v[1] + (uint32_t)other.v[1]), Wrap((uint32_t)v[2] + (uint32_t)other.v[2]), Wrap((uint32_t)v[3] + (uint32_t)other.v[3]), } };
}
Vec4S32 operator -(Vec4S32 other) const {
return Vec4S32{ { v[0] - other.v[0], v[1] - other.v[1], v[2] - other.v[2], v[3] - other.v[3], } };
return Vec4S32{ { Wrap((uint32_t)v[0] - (uint32_t)other.v[0]), Wrap((uint32_t)v[1] - (uint32_t)other.v[1]), Wrap((uint32_t)v[2] - (uint32_t)other.v[2]), Wrap((uint32_t)v[3] - (uint32_t)other.v[3]), } };
}
Vec4S32 operator *(Vec4S32 other) const {
return Vec4S32{ { v[0] * other.v[0], v[1] * other.v[1], v[2] * other.v[2], v[3] * other.v[3], } };
return Vec4S32{ { Wrap((uint32_t)v[0] * (uint32_t)other.v[0]), Wrap((uint32_t)v[1] * (uint32_t)other.v[1]), Wrap((uint32_t)v[2] * (uint32_t)other.v[2]), Wrap((uint32_t)v[3] * (uint32_t)other.v[3]), } };
}
// TODO: Can optimize the bitwise ones with 64-bit operations.
Vec4S32 operator |(Vec4S32 other) const {
@@ -1691,11 +1693,11 @@ struct Vec4S32 {
void operator &=(Vec4S32 other) { for (int i = 0; i < 4; i++) v[i] &= other.v[i]; }
void operator |=(Vec4S32 other) { for (int i = 0; i < 4; i++) v[i] |= other.v[i]; }
void operator ^=(Vec4S32 other) { for (int i = 0; i < 4; i++) v[i] ^= other.v[i]; }
void operator +=(Vec4S32 other) { for (int i = 0; i < 4; i++) v[i] += other.v[i]; }
void operator -=(Vec4S32 other) { for (int i = 0; i < 4; i++) v[i] -= other.v[i]; }
void operator +=(Vec4S32 other) { *this = *this + other; }
void operator -=(Vec4S32 other) { *this = *this - other; }
template<int imm>
Vec4S32 Shl() const { return Vec4S32{ { v[0] << imm, v[1] << imm, v[2] << imm, v[3] << imm } }; }
Vec4S32 Shl() const { return Vec4S32{ { Wrap((uint32_t)v[0] << imm), Wrap((uint32_t)v[1] << imm), Wrap((uint32_t)v[2] << imm), Wrap((uint32_t)v[3] << imm) } }; }
// Arithmetic: shifts in copies of the sign bit.
template<int imm>
Vec4S32 Shr() const { return Vec4S32{ { v[0] >> imm, v[1] >> imm, v[2] >> imm, v[3] >> imm } }; }
@@ -2166,8 +2168,13 @@ inline Vec4U16 SignBits32ToMaskU16(Vec4S32 v) {
return Vec4U16{ { (uint16_t)(v.v[0] >> 31), (uint16_t)(v.v[1] >> 31), (uint16_t)(v.v[2] >> 31), (uint16_t)(v.v[3] >> 31), } };
}
// Like SSE: out of range (and NaN) gives INT_MIN. A plain cast is undefined there, and RISC-V gives INT_MAX for NaN.
inline int32_t F32ToS32Truncate(float f) {
return (f >= -2147483648.0f && f < 2147483648.0f) ? (int32_t)f : INT32_MIN;
}
inline Vec4S32 Vec4S32FromF32(Vec4F32 f) {
return Vec4S32{ { (int32_t)f.v[0], (int32_t)f.v[1], (int32_t)f.v[2], (int32_t)f.v[3] } };
return Vec4S32{ { F32ToS32Truncate(f.v[0]), F32ToS32Truncate(f.v[1]), F32ToS32Truncate(f.v[2]), F32ToS32Truncate(f.v[3]) } };
}
inline Vec4F32 Vec4F32FromS32(Vec4S32 f) {
+5 -1
View File
@@ -153,8 +153,12 @@ inline float GERowSum(const GERowTerm *terms, int count) {
int32_t sum = 0;
for (int i = 0; i < count; ++i) {
const int32_t m = terms[i].mantissa;
if (m == 0) {
// Its lsbExp may be INT_MIN.
continue;
}
const int shift = lsbExp - terms[i].lsbExp;
if (m != 0 && shift < 32) {
if (shift < 32) {
sum += m < 0 ? -(-m >> shift) : (m >> shift);
}
}