On Sun, Aug 9, 2026 at 7:30 PM Matt Turner <[email protected]> wrote:
>
> On Sun, Aug 9, 2026 at 6:12 PM Jeffrey Law <[email protected]> 
> wrote:
> >
> > On 8/9/2026 1:12 PM, Matt Turner wrote:
> > > 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                     | 23 +++++++++++------
> > >   gcc/testsuite/gcc.target/alpha/trunctfsf2-1.c | 25 +++++++++++++++++++
> > >   2 files changed, 40 insertions(+), 8 deletions(-)
> > >   create mode 100644 gcc/testsuite/gcc.target/alpha/trunctfsf2-1.c
> > THanks.  I constructed a ChangeLog entry and pushed this to the trunk.
>
> Thanks a bunch, Jeff!
>
> (Sorry for forgetting the ChangeLog entry. Will make sure to include
> those in the future)
>
> > Not sure how much time you have Matt, but these tests started failing
> > about a month ago on alpha-linux-gnu.  No idea why yet.
> >
> > Tests that now fail, but worked before (28 tests):
> >
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O0  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O0  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O1  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O1  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O2  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O2  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O2 -flto 
> > -fno-use-linker-plugin -flto-partition=none  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O2 -flto 
> > -fno-use-linker-plugin -flto-partition=none  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O2 -flto 
> > -fuse-linker-plugin -fno-fat-lto-objects  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O2 -flto 
> > -fuse-linker-plugin -fno-fat-lto-objects  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O3 -g  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -O3 -g  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -Os  execution test
> > gcc: gcc.dg/torture/fp-int-convert-float32-timode.c   -Os  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O0  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O0  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O1  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O1  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O2  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O2  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O2 -flto 
> > -fno-use-linker-plugin -flto-partition=none  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O2 -flto 
> > -fno-use-linker-plugin -flto-partition=none  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O2 -flto -fuse-linker-plugin 
> > -fno-fat-lto-objects  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O2 -flto -fuse-linker-plugin 
> > -fno-fat-lto-objects  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O3 -g  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -O3 -g  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -Os  execution test
> > gcc: gcc.dg/torture/fp-int-convert-timode.c   -Os  execution test
> >
> >
> > 31d967232a9331ad72571da27550b2f5d7d38523 was the last good hash.  
> > f06e56614b948ad88dabf63020f47713b7dea0ec was the first reported bad hash if 
> > you're going to try and chase it down.
>
> Interesting, thanks. I'll investsigate.


Jeff,

Tracked it down. Short version: the tests are already green again on
trunk as of yesterday, but for a reason unrelated to what actually
broke them, and the real fallout is still there.

I reproduced it with a cross compiler and qemu-alpha rather than on
hardware: f06e5661 fails all ten (both tests, every -O level),
31d9672's neighborhood passes, and current trunk passes.

It is not a codegen change in the tests — the test .o is
byte-identical across the whole range. It is libgcc. Linking the same
object against each build's libgcc.a flips the result, and it narrows
to _floatdisf.o and _floatundisf.o, i.e. __floattisf and
__floatuntisf.

The chain:

6cc7df8e834 ("Remove left-overs of WIDEST_HARDWARE_FP_SIZE",
2026-07-04) deleted WIDEST_HARDWARE_FP_SIZE from alpha.h along with
every other target. libgcc/libgcc2.h still keys
AVOID_FP_TYPE_CONVERSION off that macro:

  #ifdef powerpc
  #define AVOID_FP_TYPE_CONVERSION(SIZE) (SIZE == 106)
  #elif defined(WIDEST_HARDWARE_FP_SIZE)
  #define AVOID_FP_TYPE_CONVERSION(SIZE) (SIZE > WIDEST_HARDWARE_FP_SIZE)
  #else
  #define AVOID_FP_TYPE_CONVERSION(SIZE) 0
  #endif

so it silently became 0. F_MODE_OK(LIBGCC_TF_MANT_DIG) now succeeds,
and __floattisf switched from the clz-based fallback to the TFmode
path — _OtsCvtQX, _OtsMulX, _OtsAddX, then trunctfsf2. You can see it
plainly in the disassembly: before the commit that function is the clz
sequence, after it is three _Ots calls.

(_Ots* is the quad-float ABI for alpha, dating back to the proprietary OSs)

That newly-taken trunctfsf2 was wrong, and had been for a long time.
Alpha has no TF->SF instruction, so it goes via DF and tried to avoid
double rounding by setting a sticky bit at fraction bit 48 — but it
set it before the DF conversion, and the carry out of the discarded
bits can propagate up through bit 48 and clear it, landing on exactly
the halfway value the bit was there to prevent. I fixed that
independently yesterday in aa65cd8c8d6 (round to odd at the last
fraction bit DFmode keeps), found via glibc's math testsuite. That is
why trunk is green again.

I confirmed the causality directly: re-adding AVOID_FP_TYPE_CONVERSION
for alpha in libgcc2.h on top of f06e5661 and rebuilding only libgcc
makes all ten pass.

What is still open is the libgcc2.h side. No target defines
WIDEST_HARDWARE_FP_SIZE any more — gcc/system.h poisons it — so that
#elif branch is dead code for everyone, and every target that used to
set it now silently takes the wide-FP path in __float{,un}ti{sf,df}.
rs6000 is unaffected (the powerpc branch wins), but alpha,
i386/x86_64, sparc, pa, s390, aarch64, visium and ia64-vms all changed
behavior. I checked x86_64 directly: in gcc 15's libgcc, __floattisf,
__floattidf, __floatuntisf and __floatuntidf are self-contained, and
on trunk each of them now calls out to __floatditf/__floatunditf,
__multf3, __addtf3 and __trunctfsf2/ __trunctfdf2 — five soft-float
calls per conversion where there were none. Those targets stay
correct, since soft-fp rounds once; only alpha turned it into a
wrong-answer bug, because only alpha narrows TF->SF through DF.

So I think there are two things to settle, independent of my
trunctfsf2 fix (which should stay regardless — glibc hits it):

- whether that dead #elif should just be deleted, accepting the
wide-FP path everywhere, or whether the intent should be restored
under a name libgcc can still see. The mechanism that already exists
for this is the _LIBGCC* builtin macros, so an explicit one for
libgcc's use would be the tidy version. Given the x86_64 numbers
above, "just delete it" has a real cost on the most common target, not
a hypothetical one.

- whether alpha in particular wants the fallback back. The TF path is
correct now but it is three out-of-line calls into libc's _Ots
routines where the fallback is a handful of inline instructions.

Happy to write either patch. Note that trunctfsf2 stays covered either
way: the test that came with aa65cd8c
(gcc.target/alpha/trunctfsf2-1.c) exercises the expander directly, so
it does not care which path libgcc takes for __floattisf.

Matt

Reply via email to