mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
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:
1 parent
10f5de6ded
commit
d9b36b7e46
2 files changed
+19
-8
No files matched your search
+14
-7
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user