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

            Bug ID: 126490
           Summary: Wrong code with some bitwise folds for bitint
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Keywords: wrong-code
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: ktkachov at gcc dot gnu.org
                CC: jakub at gcc dot gnu.org
  Target Milestone: ---

The following for aarch64:

/* match.pd:2717-2723

     (for first_op (bit_xor eq)
          second_op (eq bit_xor)
      (simplify
       (first_op:c (bit_and:c truth_valued_p@0 truth_valued_p@1) (second_op @0
@1))
         (bit_not (bit_ior @0 @1))))

   The comment above the rule says "-> !(a | b)" but the transform emits
   a bitwise ~.  ~X equals !X only when the value lives in a type of
   precision 1, and the rule has no condition on the type of the result.
   truth_valued_p @0/@1 are precision-1 values here (unsigned _BitInt(1)),
   but the == that is being replaced has type int, so genmatch builds the
   BIT_NOT_EXPR in int while the BIT_IOR_EXPR keeps the 1-bit type:
   ~(int)(a | b) is -1/-2 where the correct 0/1 answer is 1/0.

   a & b, a ^ b and a == b below are all evaluated in unsigned
   _BitInt(1) / int, so there is no undefined behaviour anywhere.  */

typedef unsigned _BitInt(1) u1;

/* ((a & b) == (a ^ b)) + 1.  Trunk folds the whole body to -(int)(a | b).  */
int __attribute__((noipa))
f_add (u1 a, u1 b)
{
  return ((a & b) == (a ^ b)) + 1;
}

/* ((a & b) == (a ^ b)) != 0.  Trunk folds the whole body to the constant 1,
   because ~(a | b) computed in int is -1 or -2, never zero.  */
int __attribute__((noipa))
f_ne (u1 a, u1 b)
{
  return ((a & b) == (a ^ b)) != 0;
}

/* ((a & b) == (a ^ b)) == 0.  Trunk folds the whole body to the constant 0. 
*/
int __attribute__((noipa))
f_eq (u1 a, u1 b)
{
  return ((a & b) == (a ^ b)) == 0;
}

/* ((a & b) == (a ^ b)) < 1.  Trunk folds the whole body to the constant 1.  */
int __attribute__((noipa))
f_lt (u1 a, u1 b)
{
  return ((a & b) == (a ^ b)) < 1;
}

/* Reference value of (a & b) == (a ^ b) for a, b in { 0, 1 }.  Every step
   goes through a volatile int, so the compiler cannot form the pattern.  */
int __attribute__((noipa))
ref (int ia, int ib)
{
  volatile int a = ia;
  volatile int b = ib;
  volatile int x = a & b;
  volatile int y = a ^ b;
  volatile int e = (x == y);
  return e;
}

int
main (void)
{
  static const int in[4][2] = { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } };
  int i;

  for (i = 0; i < 4; i++)
    {
      int ia = in[i][0];
      int ib = in[i][1];
      u1 a = (u1) ia;
      u1 b = (u1) ib;
      /* a & b equals a ^ b only for a == b == 0, so e is 1, 0, 0, 0.  */
      int e = ref (ia, ib);

      if (f_add (a, b) != e + 1)
        __builtin_abort ();
      if (f_ne (a, b) != (e != 0))
        __builtin_abort ();
      if (f_eq (a, b) != (e == 0))
        __builtin_abort ();
      if (f_lt (a, b) != (e < 1))
        __builtin_abort ();
    }

  return 0;
}

aborts but passes with Clang

Reply via email to