https://gcc.gnu.org/bugzilla/show_bug.cgi?id=123637

            Bug ID: 123637
           Summary: Code generation bug with 32x32->64 multiplication
           Product: gcc
           Version: 15.2.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c
          Assignee: unassigned at gcc dot gnu.org
          Reporter: [email protected]
  Target Milestone: ---

Created attachment 63364
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=63364&action=edit
Silly code that triggers the problem

I'm playing with doing some fixed-point math code that has various helpers to
deal with fixed point formats, and one of the formats is a pure 32-bit
fractional format (so no integer part, just a 32-bit unsigned fraction for
values in the [0,1[ range).

That fails on one of my tests with gcc when using -O2, but works fine with -O0
and -O1.

Interestingly, clang has the same behavior, except it also fails at -O1. That
initially made me think it's something really wrong with my code, but I really
don't see it.

I'm attaching a fairly minimal test-case - it may look a bit odd because the
real code had multiple different fixed point types wrapping them in structure
types and 'fmul()' could deal with them all using tricks that I have removed
from the test-case (and that's why I'm doing the multiply as a signed 64-bit
multiply).

When I compile it on x86-64 and run it, I get this for -O0:

    $ gcc -O -Wall minimal.c && ./a.out 
    a = 0.998000
    a*a = 0.996004
    correct = 0.996004
    wrong = -0.003996

and then for -O2 it messes up the sign, apparently doing a conversion from a
_signed_ 32-bit integer:

    $ gcc -O2 -Wall minimal.c && ./a.out 
    a = 0.998000
    a*a = -0.003996
    correct = 0.996004
    wrong = -0.003996

and I have that "asm volatile" there to defeat whatever odd optimization it is
that breaks things.

It seems to be that "32x32->signed-64 multiply followed by a 32-bit shift back
to 32 bits, followed by a conversion to double" that is needed to trigger this.
The problem does not occur if I shift by 31, or if I use an unsigned 64-bit
type

Using godbolt.org to do some minimal testing, it looks like gcc-7.5 does not
expose this behavior, but gcc-8 and following do.

Godbolt also says it happens on arm64, but (for example) icc seems to be immune
to this issue.

The fact that it happens on multiple architectures and with two different
compilers makes me think I am doing something wrong and that there is some
undefined behavior that I'm triggering.

But having looked at it a lot, I really think it's a generic "some
simplification around the 32x32->64 multiplication gets the signedness very
wrong" issue.

Reply via email to