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.