https://gcc.gnu.org/g:aa65cd8c8d69a344cffa3c63f21776d458de9914
commit r17-3167-gaa65cd8c8d69a344cffa3c63f21776d458de9914 Author: Matt Turner <[email protected]> Date: Sun Aug 9 16:06:29 2026 -0600 [PATCH] alpha: round to odd before narrowing TFmode to SFmode Alpha has no instruction converting TFmode to SFmode, so trunctfsf2 goes through DFmode and has to avoid rounding twice. It tried to, by setting a sticky bit at fraction bit 48 when the low word of the TFmode value was nonzero: a DFmode value with that bit set cannot sit exactly halfway between two SFmode values, leaving the second rounding nothing to break a tie on. But the bit is set before the conversion to DFmode, and the carry out of the bits that conversion discards can propagate up through bit 48 and clear it, landing on the halfway value it was there to avoid. Round to odd at the last fraction bit DFmode keeps instead. The value is then exactly representable in DFmode, so that conversion does not round and the one to SFmode is the only rounding. Over values constructed around SFmode halfway points the old sequence is wrong for about a quarter of them and the new one for none. Uniformly random values do not reach it: that needs the conversion to DFmode to carry all the way to a halfway value. Rounding twice also loses the underflow flag when the result is subnormal. glibc's narrowing functions compute a round-to-odd value in the wider type and convert it once, and that single conversion is what is meant to raise underflow; splitting it in two can leave the intermediate DFmode value exactly representable in SFmode, so the final step raises nothing. Found through glibc's math testsuite, where three of the narrowing fma tests returned a wrong result. Retested there on an EV67 with a glibc built by a compiler carrying this patch: the wrong results are gone, every test now reporting a maximum error of 0 ulp, and the six narrowing add and subtract tests that had been failing on the lost underflow flag pass. The narrowing tests that still fail there do so for an unrelated reason, Alpha determining tininess from the delivered result rather than as IEEE 754 describes, which no change to the compiler can address. gcc/ * config/alpha/alpha.md (trunctfsf2): Adjust sequence to avoid conversion step landing at a halfway value. gcc/testsuite/ * gcc.target/alpha/trunctfsf2-1.c: New test. Diff: --- gcc/config/alpha/alpha.md | 25 ++++++++++++++++--------- gcc/testsuite/gcc.target/alpha/trunctfsf2-1.c | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/gcc/config/alpha/alpha.md b/gcc/config/alpha/alpha.md index 30a9f613e378..2ca9dd65835d 100644 --- a/gcc/config/alpha/alpha.md +++ b/gcc/config/alpha/alpha.md @@ -2237,21 +2237,28 @@ (use (match_operand:TF 1 "general_operand"))] "TARGET_FP && TARGET_HAS_XFLOATING_LIBS" { - rtx tmpf, sticky, arg, lo, hi; + rtx tmpf, sticky, arg, lo, discarded, kept; + HOST_WIDE_INT mask = ((HOST_WIDE_INT) 1 << 60) - 1; tmpf = gen_reg_rtx (DFmode); sticky = gen_reg_rtx (DImode); arg = copy_to_mode_reg (TFmode, operands[1]); lo = gen_lowpart (DImode, arg); - hi = gen_highpart (DImode, arg); - /* Convert the low word of the TFmode value into a sticky rounding bit, - then or it into the low bit of the high word. This leaves the sticky - bit at bit 48 of the fraction, which is representable in DFmode, - which prevents rounding error in the final conversion to SFmode. */ - - emit_insn (gen_rtx_SET (sticky, gen_rtx_NE (DImode, lo, const0_rtx))); - emit_insn (gen_iordi3 (hi, hi, sticky)); + /* Round to odd at the last fraction bit DFmode keeps, so that the + conversion to DFmode is exact and the one to SFmode is the only + rounding. DFmode keeps 52 fraction bits; the high word holds the first + 48 of the 112 and the low word the rest, so that is bit 60 of the low + word. Replace everything below it with a sticky bit. */ + + discarded = expand_binop (DImode, and_optab, lo, GEN_INT (mask), + NULL_RTX, 1, OPTAB_LIB_WIDEN); + emit_insn (gen_rtx_SET (sticky, gen_rtx_NE (DImode, discarded, const0_rtx))); + kept = expand_binop (DImode, and_optab, lo, GEN_INT (~mask), + NULL_RTX, 1, OPTAB_LIB_WIDEN); + sticky = expand_shift (LSHIFT_EXPR, DImode, sticky, 60, NULL_RTX, 1); + emit_move_insn (lo, expand_binop (DImode, ior_optab, kept, sticky, + NULL_RTX, 1, OPTAB_LIB_WIDEN)); emit_insn (gen_trunctfdf2 (tmpf, arg)); emit_insn (gen_truncdfsf2 (operands[0], tmpf)); DONE; diff --git a/gcc/testsuite/gcc.target/alpha/trunctfsf2-1.c b/gcc/testsuite/gcc.target/alpha/trunctfsf2-1.c new file mode 100644 index 000000000000..14cb126d6eb5 --- /dev/null +++ b/gcc/testsuite/gcc.target/alpha/trunctfsf2-1.c @@ -0,0 +1,25 @@ +/* Test that converting long double to float rounds only once. + + Alpha has no instruction for the conversion, so it goes through DFmode. + Both values below sit just under the halfway point between two floats, + so a single correct rounding gives the lower of the two. Rounding to + DFmode first can land exactly on that halfway point, and rounding again + from there gives the upper one. */ + +/* { dg-do run } */ +/* { dg-options "-std=c99 -mieee" } */ + +extern void abort (void); + +volatile long double a = 0x1.7ff802ffffffffffffffffffffp+13L; +volatile long double b = 0x1.7ff802fffffeffffffffffffffp+13L; + +int +main (void) +{ + if ((float) a != 0x1.7ff802p+13f) + abort (); + if ((float) b != 0x1.7ff802p+13f) + abort (); + return 0; +}
