diff --git a/fpu/softfloat.c b/fpu/softfloat.c index ae6ba71854..a362bf89ca 100644 --- a/fpu/softfloat.c +++ b/fpu/softfloat.c @@ -4498,7 +4498,9 @@ floatx80 float32_to_floatx80(float32 a, float_status *status) aSign = extractFloat32Sign( a ); if ( aExp == 0xFF ) { if (aSig) { - return commonNaNToFloatx80(float32ToCommonNaN(a, status), status); + floatx80 res = commonNaNToFloatx80(float32ToCommonNaN(a, status), + status); + return floatx80_silence_nan(res, status); } return packFloatx80(aSign, floatx80_infinity_high, @@ -5016,7 +5018,9 @@ floatx80 float64_to_floatx80(float64 a, float_status *status) aSign = extractFloat64Sign( a ); if ( aExp == 0x7FF ) { if (aSig) { - return commonNaNToFloatx80(float64ToCommonNaN(a, status), status); + floatx80 res = commonNaNToFloatx80(float64ToCommonNaN(a, status), + status); + return floatx80_silence_nan(res, status); } return packFloatx80(aSign, floatx80_infinity_high, @@ -5618,7 +5622,9 @@ float32 floatx80_to_float32(floatx80 a, float_status *status) aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) { - return commonNaNToFloat32(floatx80ToCommonNaN(a, status), status); + float32 res = commonNaNToFloat32(floatx80ToCommonNaN(a, status), + status); + return float32_silence_nan(res, status); } return packFloat32( aSign, 0xFF, 0 ); } @@ -5650,7 +5656,9 @@ float64 floatx80_to_float64(floatx80 a, float_status *status) aSign = extractFloatx80Sign( a ); if ( aExp == 0x7FFF ) { if ( (uint64_t) ( aSig<<1 ) ) { - return commonNaNToFloat64(floatx80ToCommonNaN(a, status), status); + float64 res = commonNaNToFloat64(floatx80ToCommonNaN(a, status), + status); + return float64_silence_nan(res, status); } return packFloat64( aSign, 0x7FF, 0 ); } @@ -5681,7 +5689,9 @@ float128 floatx80_to_float128(floatx80 a, float_status *status) aExp = extractFloatx80Exp( a ); aSign = extractFloatx80Sign( a ); if ( ( aExp == 0x7FFF ) && (uint64_t) ( aSig<<1 ) ) { - return commonNaNToFloat128(floatx80ToCommonNaN(a, status), status); + float128 res = commonNaNToFloat128(floatx80ToCommonNaN(a, status), + status); + return float128_silence_nan(res, status); } shift128Right( aSig<<1, 0, 16, &zSig0, &zSig1 ); return packFloat128( aSign, aExp, zSig0, zSig1 ); @@ -5731,7 +5741,7 @@ floatx80 floatx80_round_to_int(floatx80 a, float_status *status) } if ( aExp < 0x3FFF ) { if ( ( aExp == 0 ) - && ( (uint64_t) ( extractFloatx80Frac( a )<<1 ) == 0 ) ) { + && ( (uint64_t) ( extractFloatx80Frac( a ) ) == 0 ) ) { return a; } status->float_exception_flags |= float_flag_inexact; @@ -5856,6 +5866,12 @@ static floatx80 addFloatx80Sigs(floatx80 a, floatx80 b, flag zSign, zSig1 = 0; zSig0 = aSig + bSig; if ( aExp == 0 ) { + if ((aSig | bSig) & UINT64_C(0x8000000000000000) && zSig0 < aSig) { + /* At least one of the values is a pseudo-denormal, + * and there is a carry out of the result. */ + zExp = 1; + goto shiftRight1; + } if (zSig0 == 0) { return packFloatx80(zSign, 0, 0); } @@ -6959,7 +6975,9 @@ floatx80 float128_to_floatx80(float128 a, float_status *status) aSign = extractFloat128Sign( a ); if ( aExp == 0x7FFF ) { if ( aSig0 | aSig1 ) { - return commonNaNToFloatx80(float128ToCommonNaN(a, status), status); + floatx80 res = commonNaNToFloatx80(float128ToCommonNaN(a, status), + status); + return floatx80_silence_nan(res, status); } return packFloatx80(aSign, floatx80_infinity_high, floatx80_infinity_low); @@ -7948,6 +7966,13 @@ static inline int floatx80_compare_internal(floatx80 a, floatx80 b, return 1 - (2 * aSign); } } else { + /* Normalize pseudo-denormals before comparison. */ + if ((a.high & 0x7fff) == 0 && a.low & UINT64_C(0x8000000000000000)) { + ++a.high; + } + if ((b.high & 0x7fff) == 0 && b.low & UINT64_C(0x8000000000000000)) { + ++b.high; + } if (a.low == b.low && a.high == b.high) { return float_relation_equal; } else { diff --git a/tests/tcg/i386/test-i386-pseudo-denormal.c b/tests/tcg/i386/test-i386-pseudo-denormal.c new file mode 100644 index 0000000000..00d510cf4a --- /dev/null +++ b/tests/tcg/i386/test-i386-pseudo-denormal.c @@ -0,0 +1,38 @@ +/* Test pseudo-denormal operations. */ + +#include +#include + +union u { + struct { uint64_t sig; uint16_t sign_exp; } s; + long double ld; +}; + +volatile union u ld_pseudo_m16382 = { .s = { UINT64_C(1) << 63, 0 } }; + +volatile long double ld_res; + +int main(void) +{ + short cw; + int ret = 0; + ld_res = ld_pseudo_m16382.ld + ld_pseudo_m16382.ld; + if (ld_res != 0x1p-16381L) { + printf("FAIL: pseudo-denormal add\n"); + ret = 1; + } + if (ld_pseudo_m16382.ld != 0x1p-16382L) { + printf("FAIL: pseudo-denormal compare\n"); + ret = 1; + } + /* Set round-upward. */ + __asm__ volatile ("fnstcw %0" : "=m" (cw)); + cw = (cw & ~0xc00) | 0x800; + __asm__ volatile ("fldcw %0" : : "m" (cw)); + __asm__ ("frndint" : "=t" (ld_res) : "0" (ld_pseudo_m16382.ld)); + if (ld_res != 1.0L) { + printf("FAIL: pseudo-denormal round-to-integer\n"); + ret = 1; + } + return ret; +} diff --git a/tests/tcg/i386/test-i386-snan-convert.c b/tests/tcg/i386/test-i386-snan-convert.c new file mode 100644 index 0000000000..ed6d535ce2 --- /dev/null +++ b/tests/tcg/i386/test-i386-snan-convert.c @@ -0,0 +1,63 @@ +/* Test conversions of signaling NaNs to and from long double. */ + +#include +#include + +volatile float f_res; +volatile double d_res; +volatile long double ld_res; + +volatile float f_snan = __builtin_nansf(""); +volatile double d_snan = __builtin_nans(""); +volatile long double ld_snan = __builtin_nansl(""); + +int issignaling_f(float x) +{ + union { float f; uint32_t u; } u = { .f = x }; + return (u.u & 0x7fffffff) > 0x7f800000 && (u.u & 0x400000) == 0; +} + +int issignaling_d(double x) +{ + union { double d; uint64_t u; } u = { .d = x }; + return (((u.u & UINT64_C(0x7fffffffffffffff)) > + UINT64_C(0x7ff0000000000000)) && + (u.u & UINT64_C(0x8000000000000)) == 0); +} + +int issignaling_ld(long double x) +{ + union { + long double ld; + struct { uint64_t sig; uint16_t sign_exp; } s; + } u = { .ld = x }; + return ((u.s.sign_exp & 0x7fff) == 0x7fff && + (u.s.sig >> 63) != 0 && + (u.s.sig & UINT64_C(0x4000000000000000)) == 0); +} + +int main(void) +{ + int ret = 0; + ld_res = f_snan; + if (issignaling_ld(ld_res)) { + printf("FAIL: float -> long double\n"); + ret = 1; + } + ld_res = d_snan; + if (issignaling_ld(ld_res)) { + printf("FAIL: double -> long double\n"); + ret = 1; + } + f_res = ld_snan; + if (issignaling_d(f_res)) { + printf("FAIL: long double -> float\n"); + ret = 1; + } + d_res = ld_snan; + if (issignaling_d(d_res)) { + printf("FAIL: long double -> double\n"); + ret = 1; + } + return ret; +}