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

            Bug ID: 127760
           Summary: [15/16/17 Regression] wrong code emitted on
                    almost-SAT_ADD code
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Keywords: missed-optimization, wrong-code
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: gabravier at gmail dot com
  Target Milestone: ---

#include <stdio.h>
#include <limits.h>

__attribute__((noipa)) unsigned
u_sat (unsigned a, unsigned b)
{
  unsigned char r;
  if (__builtin_add_overflow (a, b, &r))
    return -1u;
  return a + b;
}

__attribute__((noipa)) int
s_sat (int x)
{
  int sum = (int) ((unsigned) x + 1u);
  if (((x ^ sum) & (x ^ 2)) < 0)
    return -(int) (x < 0) ^ INT_MAX;
  return sum;
}

__attribute__((noipa)) signed char
another_sat (int a, int b)
{
  signed char r;
  if (__builtin_add_overflow (a, b, &r))
    return a < 0 ? SCHAR_MIN : SCHAR_MAX;
  return r;
}

int main (void)
{
  printf ("u_sat(200,100): %u  (expected %u)\n", u_sat (200, 100), UINT_MAX);
  printf ("s_sat(INT_MAX): %d  (expected %d)\n", s_sat (INT_MAX), INT_MIN);
  printf ("s_sat(-1):      %d  (expected %d)\n", s_sat (-1), INT_MIN);
  printf ("another_sat(-1, 200): %d (expected -128)\n", another_sat(-1, 200));
  printf ("another_sat(100, 100): %d (expected 127)\n", another_sat(100, 100));
  printf ("another_sat(1, 2): %d (expected 3)\n", another_sat(1, 2));

  return 0;
}

The above program should, as far as I can see, output:

u_sat(200,100): 4294967295  (expected 4294967295)
s_sat(INT_MAX): -2147483648  (expected -2147483648)
s_sat(-1):      -2147483648  (expected -2147483648)
another_sat(-1, 200): -128 (expected -128)
another_sat(100, 100): 127 (expected 127)
another_sat(1, 2): 3 (expected 3)

It does so on RISC-V 64 GCC 14.3.0, with -O2. RISC-V 64 GCC trunk
(7781ff63bf3c43e5620810048bc9c5cfe3f447e1), with -O2, instead outputs:

u_sat(200,100): 300  (expected 4294967295)
s_sat(INT_MAX): 2147483647  (expected -2147483648)
s_sat(-1):      0  (expected -2147483648)
another_sat(-1, 200): -57 (expected -128)
another_sat(100, 100): -56 (expected 127)
another_sat(1, 2): 3 (expected 3)

The assembly output is as such:

u_sat:
        slli    a0,a0,32
        srli    t0,a0,32
        add     a1,a1,t0
        slli    t1,a1,32
        srli    t2,t1,32
        sltu    a2,t2,t0
        neg     a3,a2
        or      a4,a3,t2
        sext.w  a0,a4
        ret
s_sat:
        addi    a3,a0,1
        xor     a5,a0,a3
        srli    a4,a0,31
        srli    t0,a5,31
        xori    t1,a4,1
        and     t2,t0,t1
        li      a1,-2147483648
        andi    a6,t2,1
        srai    a0,a0,63
        not     a2,a1
        xor     a7,a2,a0
        neg     t3,a6
        addi    t4,a6,-1
        and     t5,a7,t3
        and     t6,a3,t4
        or      a3,t6,t5
        sext.w  a0,a3
        ret
another_sat:
        add     a3,a0,a1
        xor     a1,a0,a1
        xor     a5,a0,a3
        srli    t0,a1,31
        xori    t1,t0,1
        srli    t2,a5,31
        and     a2,t2,t1
        li      a4,-2147483648
        andi    a6,a2,1
        srai    a0,a0,63
        not     a7,a4
        xor     t3,a7,a0
        neg     t4,a6
        addi    t5,a6,-1
        and     t6,t3,t4
        and     a3,a3,t5
        or      a1,a3,t6
        slliw   t0,a1,24
        sraiw   a0,t0,24
        ret

...which seems to compute different functions. u_sat is effectively `(a + b) |
-((a + b) < a);`, s_sat is effectively `x == INT_MAX ? INT_MAX : x + 1;`, and
another_sat is effectively `int s; return (signed char) (ckd_add (&s, a, b) ?
(a < 0 ? INT_MIN : INT_MAX) : s);`, which are not equivalent functions to the
functions in the above program.

I believe the cause is the following pattern in match-sat-alu.pd (introduced by
r17-4076-g28f6899c51c456):

 (match (unsigned_integer_sat_add @0 @1)
  /* SUM = ADD_OVERFLOW (X, Y)
     SAT_U_ADD = IMAGPART (SUM) != 0 ? -1 : X + Y.  */
  (cond^ (ne (imagpart (IFN_ADD_OVERFLOW @0 @1)) integer_zerop)
         integer_minus_onep (plus @0 @1)))

...which does not appear to check the result type of the IFN_ADD_OVERFLOW -
which makes an 8-bit overflow test be accepted as a 32-bit one, and (introduced
by r15-4994-gda31786910f253):

 (match (signed_integer_sat_add @0 @1)
  /* T SUM = (T)((UT)X + (UT)IMM);
     SAT_S_ADD = (X ^ IMM) < 0 ? SUM : (X ^ SUM) >= 0 ? SUM
                                                      : (x < 0) ? MIN : MAX  */
  (cond^ (lt (bit_and:c (bit_xor:c @0 (nop_convert@2 (plus (nop_convert @0)
                                                           INTEGER_CST@1)))
                        (bit_xor:c @0 INTEGER_CST@3)) integer_zerop)
         (signed_integer_sat_val @0)
         @2)
  (if (wi::bit_and (wi::to_wide (@1), wi::to_wide (@3)) == 0)))

...which does not appear to check that the sign of @1 is the opposite of the
sign of @3, making a test opposite to the overflow test be compiled as if it
were the overflow test. This also presumably misses constants it could accept:
e.g. with @1 = 1 and @3 -3, 1 & -3 != 0, so it wouldn't be accepted despite
being a valid overflow test

...and (introduced by r15-6406-g59e3abcb3625fd):

 (match (signed_integer_sat_add @0 @1)
   /* SUM = .ADD_OVERFLOW (X, Y)
      SAT_S_ADD = IMAGPART_EXPR (SUM) != 0 ? (-(T)(X < 0) ^ MAX) : SUM  */
  (cond^ (ne (imagpart (IFN_ADD_OVERFLOW:c@2 @0 @1)) integer_zerop)
         (signed_integer_sat_val @0)
         (realpart @2)))

which lacks an if clause that would test for the result not matching the types
of the operands.

(note that I have not actually personally bisected this or tested making the
appropriate changes - this is merely what appears to me to be the causes)

Note that the program also fails to output correctly on x86, but only for the
u_sat line (for which it gives the RISC-V output), which I believe is merely
because of the lack of a scalar signed saturating-add pattern on x86.

Reply via email to